Przeglądaj źródła

[Compose] Update watchdog image
[Watchdog] Fix IP detection with multiple networks
[Web] Show API field (no docs, no support, wip)
[Web] haveibeenpwned.com implementation
[Web] User and domain admin ACL (no docs, no support, wip)
[Web] Some minor fixes

André 7 lat temu
rodzic
commit
c9554ca022

+ 16 - 11
data/Dockerfiles/watchdog/watchdog.sh

@@ -59,10 +59,10 @@ function mail_error() {
   log_msg "Sent notification email to ${1}"
   log_msg "Sent notification email to ${1}"
 }
 }
 
 
-
 get_container_ip() {
 get_container_ip() {
   # ${1} is container
   # ${1} is container
   CONTAINER_ID=()
   CONTAINER_ID=()
+  CONTAINER_IPS=()
   CONTAINER_IP=
   CONTAINER_IP=
   LOOP_C=1
   LOOP_C=1
   until [[ ${CONTAINER_IP} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || [[ ${LOOP_C} -gt 5 ]]; do
   until [[ ${CONTAINER_IP} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || [[ ${LOOP_C} -gt 5 ]]; do
@@ -72,15 +72,21 @@ get_container_ip() {
     # returned id can have multiple elements (if scaled), shuffle for random test
     # returned id can have multiple elements (if scaled), shuffle for random test
     CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf))
     CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf))
     if [[ ! -z ${CONTAINER_ID} ]]; then
     if [[ ! -z ${CONTAINER_ID} ]]; then
-      for matched_container in "${CONTAINER_ID[@]}"; do
-        CONTAINER_IP=$(curl --silent http://dockerapi:8080/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress')
-        # grep will do nothing if one of these vars is empty
-        [[ -z ${CONTAINER_IP} ]] && continue
-        [[ -z ${IPV4_NETWORK} ]] && continue
-        # only return ips that are part of our network
-        if ! grep -q ${IPV4_NETWORK} <(echo ${CONTAINER_IP}); then
-          CONTAINER_IP=
-        fi
+      for matched_container in "${CONTAINER_ID[@]}"; do 
+        CONTAINER_IPS=($(curl --silent http://dockerapi:8080/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress')) 
+        for ip_match in "${CONTAINER_IPS[@]}"; do 
+          # grep will do nothing if one of these vars is empty
+          [[ -z ${ip_match} ]] && continue
+          [[ -z ${IPV4_NETWORK} ]] && continue
+          # only return ips that are part of our network
+          if ! grep -q ${IPV4_NETWORK} <(echo ${ip_match}); then
+            continue
+          else
+            CONTAINER_IP=${ip_match}
+            break
+          fi
+        done
+        [[ ! -z ${CONTAINER_IP} ]] && break
       done
       done
     fi
     fi
     LOOP_C=$((LOOP_C + 1))
     LOOP_C=$((LOOP_C + 1))
@@ -88,7 +94,6 @@ get_container_ip() {
   [[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP}
   [[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP}
 }
 }
 
 
-# Check functions
 nginx_checks() {
 nginx_checks() {
   err_count=0
   err_count=0
   diff_c=0
   diff_c=0

+ 7 - 8
data/web/admin.php

@@ -29,7 +29,7 @@ $tfa_data = get_tfa();
           <div class="form-group">
           <div class="form-group">
             <label class="control-label col-sm-3" for="admin_pass"><?=$lang['admin']['password'];?>:</label>
             <label class="control-label col-sm-3" for="admin_pass"><?=$lang['admin']['password'];?>:</label>
             <div class="col-sm-9">
             <div class="col-sm-9">
-            <input type="password" class="form-control" name="admin_pass" id="admin_pass" placeholder="<?=$lang['admin']['unchanged_if_empty'];?>">
+            <input type="password" data-hibp="true" class="form-control" name="admin_pass" id="admin_pass" placeholder="<?=$lang['admin']['unchanged_if_empty'];?>">
             </div>
             </div>
           </div>
           </div>
           <div class="form-group">
           <div class="form-group">
@@ -44,7 +44,7 @@ $tfa_data = get_tfa();
             </div>
             </div>
           </div>
           </div>
         </form>
         </form>
-        <hr>
+        <legend><?=$lang['tfa']['tfa'];?></legend>
         <div class="row">
         <div class="row">
           <div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?>:</div>
           <div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?>:</div>
           <div class="col-sm-9 col-xs-7">
           <div class="col-sm-9 col-xs-7">
@@ -76,12 +76,10 @@ $tfa_data = get_tfa();
             </select>
             </select>
           </div>
           </div>
         </div>
         </div>
-      </div>
-    </div>
-
-    <div class="hidden panel panel-primary">
-      <div class="panel-heading">API</div>
-      <div class="panel-body">
+        <legend data-target="#api" style="margin-top:40px;cursor:pointer" id="api_legend" unselectable="on" data-toggle="collapse">
+          <span id="api_arrow" style="font-size:12px" class="rotate glyphicon glyphicon-menu-down"></span> API (experimental, work in progress)
+        </legend>
+        <div id="api" class="collapse">
         <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
         <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
           <div class="form-group">
           <div class="form-group">
             <label class="control-label col-sm-3" for="allow_from"><?=$lang['admin']['api_allow_from'];?>:</label>
             <label class="control-label col-sm-3" for="allow_from"><?=$lang['admin']['api_allow_from'];?>:</label>
@@ -111,6 +109,7 @@ $tfa_data = get_tfa();
             </div>
             </div>
           </div>
           </div>
         </form>
         </form>
+        </div>
       </div>
       </div>
     </div>
     </div>
 
 

+ 0 - 6
data/web/css/admin.css

@@ -65,12 +65,6 @@ body.modal-open {
   font-size:9pt;
   font-size:9pt;
   background:transparent;
   background:transparent;
 }
 }
-.bootstrap-select {
-  width: auto!important;
-}
 .table-condensed .input-sm {
 .table-condensed .input-sm {
   width: 100%!important;  
   width: 100%!important;  
 }
 }
-.full-width-select {
-  width: 100%!important;  
-}

+ 0 - 3
data/web/css/mailbox.css

@@ -5,9 +5,6 @@ table.footable>tbody>tr.footable-empty>td {
 .pagination a {
 .pagination a {
   text-decoration: none !important;
   text-decoration: none !important;
 }
 }
-.panel panel-default {
-  overflow: visible !important;
-}
 .btn-group {
 .btn-group {
   width: max-content;
   width: max-content;
 }
 }

+ 10 - 0
data/web/css/mailcow.css

@@ -148,3 +148,13 @@ nav .glyphicon {
   color: #5a5a5a;
   color: #5a5a5a;
   white-space: nowrap;
   white-space: nowrap;
 }
 }
+.haveibeenpwned {
+  cursor: pointer;
+  -webkit-user-select: none;  
+  -moz-user-select: none;    
+  -ms-user-select: none;      
+  user-select: none;
+}
+.full-width-select {
+  width: 100%!important;  
+}

+ 58 - 10
data/web/edit.php

@@ -92,7 +92,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
             <div class="form-group">
             <div class="form-group">
               <label class="control-label col-sm-2" for="domains"><?=$lang['edit']['domains'];?></label>
               <label class="control-label col-sm-2" for="domains"><?=$lang['edit']['domains'];?></label>
               <div class="col-sm-10">
               <div class="col-sm-10">
-                <select data-live-search="true" id="domains" name="domains" multiple required>
+                <select data-live-search="true" class="full-width-select" id="domains" name="domains" multiple required>
                 <?php
                 <?php
                 foreach ($result['selected_domains'] as $domain):
                 foreach ($result['selected_domains'] as $domain):
                 ?>
                 ?>
@@ -111,7 +111,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
             <div class="form-group">
             <div class="form-group">
               <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
               <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
               <div class="col-sm-10">
               <div class="col-sm-10">
-              <input type="password" class="form-control" name="password" id="password" placeholder="">
+              <input type="password" data-hibp="true" class="form-control" name="password" id="password" placeholder="">
               </div>
               </div>
             </div>
             </div>
             <div class="form-group">
             <div class="form-group">
@@ -140,6 +140,30 @@ if (isset($_SESSION['mailcow_cc_role'])) {
               </div>
               </div>
             </div>
             </div>
           </form>
           </form>
+          <form data-id="daacl" class="form-inline well" method="post">
+            <div class="row">
+              <div class="col-sm-1">
+                <p class="help-block">ACL</p>
+              </div>
+              <div class="col-sm-10">
+                <div class="form-group">
+                  <select id="da_acl" name="da_acl" size="10" multiple>
+                  <?php
+                  $da_acls = acl('get', 'domainadmin', $domain_admin);
+                  foreach ($da_acls as $acl => $val):
+                    ?>
+                    <option value="<?=$acl;?>" <?=($val == 1) ? 'selected' : null;?>><?=$lang['acl'][$acl];?></option>
+                    <?php
+                  endforeach;
+                  ?>
+                  </select>
+                </div>
+                <div class="form-group">
+                  <button class="btn btn-default" id="edit_selected" data-id="daacl" data-item="<?=htmlspecialchars($domain_admin);?>" data-api-url='edit/da-acl' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
+                </div>
+              </div>
+            </div>
+          </form>
         <?php
         <?php
         }
         }
         else {
         else {
@@ -266,7 +290,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
           </select>
           </select>
         </div>
         </div>
         <div class="form-group">
         <div class="form-group">
-          <button class="btn btn-default" id="edit_selected" data-id="domratelimit" data-item="<?=$domain;?>" data-api-url='edit/rl-domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
+          <button data-acl="<?=$_SESSION['acl']['ratelimit'];?>" class="btn btn-default" id="edit_selected" data-id="domratelimit" data-item="<?=$domain;?>" data-api-url='edit/rl-domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
         </div>
         </div>
       </form>
       </form>
       <hr>
       <hr>
@@ -278,14 +302,14 @@ if (isset($_SESSION['mailcow_cc_role'])) {
             <table class="table table-striped table-condensed" id="wl_policy_domain_table"></table>
             <table class="table table-striped table-condensed" id="wl_policy_domain_table"></table>
           </div>
           </div>
           <div class="mass-actions-user">
           <div class="mass-actions-user">
-            <div class="btn-group">
+            <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
               <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
               <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
               <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
               <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
               </ul>
               </ul>
             </div>
             </div>
           </div>
           </div>
           <form class="form-inline" data-id="add_wl_policy_domain">
           <form class="form-inline" data-id="add_wl_policy_domain">
-            <div class="input-group">
+            <div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
               <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
               <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
               <span class="input-group-btn">
               <span class="input-group-btn">
                 <button class="btn btn-default" id="add_item" data-id="add_wl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"wl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button>
                 <button class="btn btn-default" id="add_item" data-id="add_wl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"wl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button>
@@ -300,14 +324,14 @@ if (isset($_SESSION['mailcow_cc_role'])) {
             <table class="table table-striped table-condensed" id="bl_policy_domain_table"></table>
             <table class="table table-striped table-condensed" id="bl_policy_domain_table"></table>
           </div>
           </div>
           <div class="mass-actions-user">
           <div class="mass-actions-user">
-            <div class="btn-group">
+            <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
               <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
               <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
               <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
               <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
               </ul>
               </ul>
             </div>
             </div>
           </div>
           </div>
           <form class="form-inline" data-id="add_bl_policy_domain">
           <form class="form-inline" data-id="add_bl_policy_domain">
-            <div class="input-group">
+            <div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
               <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
               <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
               <span class="input-group-btn">
               <span class="input-group-btn">
                 <button class="btn btn-default" id="add_item" data-id="add_bl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"bl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button>
                 <button class="btn btn-default" id="add_item" data-id="add_bl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"bl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button>
@@ -474,7 +498,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
           <div class="form-group">
           <div class="form-group">
             <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
             <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
             <div class="col-sm-10">
             <div class="col-sm-10">
-            <input type="password" class="form-control" name="password" id="password" placeholder="<?=$lang['edit']['unchanged_if_empty'];?>">
+            <input type="password" data-hibp="true" class="form-control" name="password" id="password" placeholder="<?=$lang['edit']['unchanged_if_empty'];?>">
             </div>
             </div>
           </div>
           </div>
           <div class="form-group">
           <div class="form-group">
@@ -527,6 +551,30 @@ if (isset($_SESSION['mailcow_cc_role'])) {
             </div>
             </div>
           </div>
           </div>
         </form>
         </form>
+        <form data-id="useracl" class="form-inline well" method="post">
+          <div class="row">
+            <div class="col-sm-1">
+              <p class="help-block">ACL</p>
+            </div>
+            <div class="col-sm-10">
+              <div class="form-group">
+                <select id="user_acl" name="user_acl" size="10" multiple>
+                <?php
+                $user_acls = acl('get', 'user', $mailbox);
+                foreach ($user_acls as $acl => $val):
+                  ?>
+                  <option value="<?=$acl;?>" <?=($val == 1) ? 'selected' : null;?>><?=$lang['acl'][$acl];?></option>
+                  <?php
+                endforeach;
+                ?>
+                </select>
+              </div>
+              <div class="form-group">
+                <button class="btn btn-default" id="edit_selected" data-id="useracl" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/user-acl' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
+              </div>
+            </div>
+          </div>
+        </form>
       <?php
       <?php
       }
       }
     }
     }
@@ -553,7 +601,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
             <div class="form-group">
             <div class="form-group">
               <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label>
               <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label>
               <div class="col-sm-10">
               <div class="col-sm-10">
-                <input type="password" class="form-control" name="password" id="password" value="<?=htmlspecialchars($result['password'], ENT_QUOTES, 'UTF-8');?>">
+                <input type="password" data-hibp="true" class="form-control" name="password" id="password" value="<?=htmlspecialchars($result['password'], ENT_QUOTES, 'UTF-8');?>">
               </div>
               </div>
             </div>
             </div>
             <div class="form-group">
             <div class="form-group">
@@ -965,7 +1013,7 @@ else {
 <script type='text/javascript'>
 <script type='text/javascript'>
 <?php
 <?php
 $lang_user = json_encode($lang['user']);
 $lang_user = json_encode($lang['user']);
-echo "var lang = ". $lang_user . ";\n";
+echo "var lang_user = ". $lang_user . ";\n";
 echo "var table_for_domain = '". ((isset($domain)) ? $domain : null) . "';\n";
 echo "var table_for_domain = '". ((isset($domain)) ? $domain : null) . "';\n";
 echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
 echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
 echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
 echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";

+ 15 - 102
data/web/inc/footer.inc.php

@@ -12,10 +12,20 @@ logger();
 <script src="/js/formcache.min.js"></script>
 <script src="/js/formcache.min.js"></script>
 <script src="/js/google.charts.loader.js"></script>
 <script src="/js/google.charts.loader.js"></script>
 <script src="/js/numberedtextarea.min.js"></script>
 <script src="/js/numberedtextarea.min.js"></script>
+<script src="/js/sha1.min.js"></script>
 <script src="/js/u2f-api.js"></script>
 <script src="/js/u2f-api.js"></script>
 <script src="/js/api.js"></script>
 <script src="/js/api.js"></script>
+<script src="/js/mailcow.js"></script>
 <script>
 <script>
-var loading_text = '<?= $lang['footer']['loading']; ?>'
+<?php
+$lang_footer = json_encode($lang['footer']);
+$lang_acl = json_encode($lang['acl']);
+$lang_tfa = json_encode($lang['tfa']);
+echo "var lang_footer = ". $lang_footer . ";\n";
+echo "var lang_acl = ". $lang_acl . ";\n";
+echo "var lang_tfa = ". $lang_tfa . ";\n";
+echo "var docker_timeout = ". $DOCKER_TIMEOUT * 1000 . ";\n";
+?>
 $(window).scroll(function() {
 $(window).scroll(function() {
   sessionStorage.scrollTop = $(this).scrollTop();
   sessionStorage.scrollTop = $(this).scrollTop();
 });
 });
@@ -28,17 +38,8 @@ $(window).load(function() {
   $(".overlay").hide();
   $(".overlay").hide();
 });
 });
 $(document).ready(function() {
 $(document).ready(function() {
-  window.mailcow_alert_box = function(message, type) {
-    msg = $('<span/>').text(message).text();
-    if (type == 'danger') {
-      auto_hide = 0;
-      $('#' + localStorage.getItem("add_modal")).modal('show');
-      localStorage.removeItem("add_modal");
-    } else {
-      auto_hide = 5000;
-    }
-    $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
-  }
+  // TFA, CSRF, Alerts in footer.inc.php
+  // Other general functions in mailcow.js
   <?php
   <?php
   $alertbox_log_parser = alertbox_log_parser($_SESSION);
   $alertbox_log_parser = alertbox_log_parser($_SESSION);
   if (is_array($alertbox_log_parser)) {
   if (is_array($alertbox_log_parser)) {
@@ -50,7 +51,6 @@ $(document).ready(function() {
   unset($_SESSION['return']);
   unset($_SESSION['return']);
   }
   }
   ?>
   ?>
-  $('[data-cached-form="true"]').formcache({key: $(this).data('id')});
   // Confirm TFA modal
   // Confirm TFA modal
   <?php if (isset($_SESSION['pending_tfa_method'])):?>
   <?php if (isset($_SESSION['pending_tfa_method'])):?>
   $('#ConfirmTFAModal').modal({
   $('#ConfirmTFAModal').modal({
@@ -68,7 +68,7 @@ $(document).ready(function() {
           dataType: 'script',
           dataType: 'script',
           url: "/api/v1/get/u2f-authentication/<?= (isset($_SESSION['pending_mailcow_cc_username'])) ? rawurlencode($_SESSION['pending_mailcow_cc_username']) : null; ?>",
           url: "/api/v1/get/u2f-authentication/<?= (isset($_SESSION['pending_mailcow_cc_username'])) ? rawurlencode($_SESSION['pending_mailcow_cc_username']) : null; ?>",
           complete: function(data){
           complete: function(data){
-            $('#u2f_status_auth').html('<?=$lang['tfa']['waiting_usb_auth'];?>');
+            $('#u2f_status_auth').html(lang_tfa.waiting_usb_auth);
             data;
             data;
             setTimeout(function() {
             setTimeout(function() {
               console.log("Ready to authenticate");
               console.log("Ready to authenticate");
@@ -98,7 +98,6 @@ $(document).ready(function() {
   <?php endif; ?>
   <?php endif; ?>
 
 
   // Set TFA modals
   // Set TFA modals
-
   $('#selectTFA').change(function () {
   $('#selectTFA').change(function () {
     if ($(this).val() == "yubi_otp") {
     if ($(this).val() == "yubi_otp") {
       $('#YubiOTPModal').modal('show');
       $('#YubiOTPModal').modal('show');
@@ -121,7 +120,7 @@ $(document).ready(function() {
           data;
           data;
           setTimeout(function() {
           setTimeout(function() {
             console.log("Ready to register");
             console.log("Ready to register");
-            $('#u2f_status_reg').html('<?=$lang['tfa']['waiting_usb_register'];?>');
+            $('#u2f_status_reg').html(lang_tfa.waiting_usb_register);
             u2f.register(appId, registerRequests, registeredKeys, function(deviceResponse) {
             u2f.register(appId, registerRequests, registeredKeys, function(deviceResponse) {
               var form  = document.getElementById('u2f_reg_form');
               var form  = document.getElementById('u2f_reg_form');
               var reg   = document.getElementById('u2f_register_data');
               var reg   = document.getElementById('u2f_register_data');
@@ -146,92 +145,6 @@ $(document).ready(function() {
     }
     }
   });
   });
 
 
-  $(function () {
-    $('[data-toggle="tooltip"]').tooltip()
-  });
-
-  // Remember last navigation pill
-  (function () {
-    'use strict';
-    if ($('a[data-toggle="tab"]').length) {
-      $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
-        if ($(this).data('dont-remember') == 1) {
-          return true;
-        }
-        var id = $(this).parents('[role="tablist"]').attr('id');
-        var key = 'lastTag';
-        if (id) {
-          key += ':' + id;
-        }
-        localStorage.setItem(key, $(e.target).attr('href'));
-      });
-      $('[role="tablist"]').each(function (idx, elem) {
-        var id = $(elem).attr('id');
-        var key = 'lastTag';
-        if (id) {
-          key += ':' + id;
-        }
-        var lastTab = localStorage.getItem(key);
-        if (lastTab) {
-          $('[href="' + lastTab + '"]').tab('show');
-        }
-      });
-    }
-  })();
-
-  // Disable submit after submitting form (not API driven buttons)
-  $('form').submit(function() {
-    if ($('form button[type="submit"]').data('submitted') == '1') {
-      return false;
-    } else {
-      $(this).find('button[type="submit"]').first().text('<?= $lang['footer']['loading']; ?>');
-      $('form button[type="submit"]').attr('data-submitted', '1');
-      function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); };
-      $(document).on("keydown", disableF5);
-    }
-  });
-
-  // IE fix to hide scrollbars when table body is empty
-  $('tbody').filter(function (index) {
-    return $(this).children().length < 1;
-  }).remove();
-
-  // Init Bootstrap Selectpicker
-  $('select').selectpicker();
-
-  // Trigger container restart
-  $('#RestartContainer').on('show.bs.modal', function(e) {
-    var container = $(e.relatedTarget).data('container');
-    $('#containerName').text(container);
-    $('#triggerRestartContainer').click(function(){
-      $(this).prop("disabled",true);
-      $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> ');
-      $('#statusTriggerRestartContainer').html('<?= $lang['footer']['restarting_container']; ?>');
-      $.ajax({
-        method: 'get',
-        url: '/inc/ajax/container_ctrl.php',
-        timeout: <?= $DOCKER_TIMEOUT * 1000; ?>,
-        data: {
-        'service': container,
-        'action': 'restart'
-        }
-      })
-      .always( function (data, status) {
-        $('#statusTriggerRestartContainer').append(data);
-        var htmlResponse = $.parseHTML(data)
-        if ($(htmlResponse).find('span').hasClass('text-success')) {
-          $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-ok"></span> ');
-          setTimeout(function(){
-            $('#RestartContainer').modal('toggle'); 
-            window.location = window.location.href.split("#")[0];
-          }, 1200);
-        } else {
-          $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-remove"></span> ');
-        }
-      })
-    });
-  })
-
   // CSRF
   // CSRF
   $('<input type="hidden" value="<?= $_SESSION['CSRF']['TOKEN']; ?>">').attr('id', 'csrf_token').attr('name', 'csrf_token').appendTo('form');
   $('<input type="hidden" value="<?= $_SESSION['CSRF']['TOKEN']; ?>">').attr('id', 'csrf_token').attr('name', 'csrf_token').appendTo('form');
   if (sessionStorage.scrollTop != "undefined") {
   if (sessionStorage.scrollTop != "undefined") {

+ 216 - 0
data/web/inc/functions.acl.inc.php

@@ -0,0 +1,216 @@
+<?php
+function acl($_action, $_scope = null, $_data = null) {
+  global $pdo;
+  global $lang;
+  $_data_log = $_data;
+  switch ($_action) {
+    case 'edit':
+      switch ($_scope) {
+        case 'user':
+          if (!is_array($_data['username'])) {
+            $usernames = array();
+            $usernames[] = $_data['username'];
+          }
+          else {
+            $usernames = $_data['username'];
+          }
+          foreach ($usernames as $username) {
+            // Cast to array for single selections
+            $acls = (array)$_data['user_acl'];
+            // Create associative array from index array
+            // All set items are given 1 as value
+            foreach ($acls as $acl_key => $acl_val) {
+              $acl_post[$acl_val] = 1;
+            }
+            // Users cannot change their own ACL
+            if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)
+              || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) {
+              $_SESSION['return'][] = array(
+                'type' => 'danger',
+                'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+                'msg' => 'access_denied'
+              );
+              continue;
+            }
+            // Read all available acl options by calling acl(get)
+            // Set all available acl options we cannot find in the post data to 0, else 1
+            $is_now = acl('get', 'user', $username);
+            if (!empty($is_now)) {
+              foreach ($is_now as $acl_now_name => $acl_now_val) {
+                $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0;
+              }
+            }
+            else {
+              $_SESSION['return'][] = array(
+                'type' => 'danger',
+                'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+                'msg' => 'Cannot determine current ACL'
+              );
+              continue;
+            }
+            foreach ($set_acls as $set_acl_key => $set_acl_val) {
+              $stmt = $pdo->prepare("UPDATE `user_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . "
+                WHERE `username` = :username");
+              $stmt->execute(array(
+                ':username' => $username,
+              ));
+            }
+            $_SESSION['return'][] = array(
+              'type' => 'success',
+              'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+              'msg' => array('acl_saved', $username)
+            );
+          }
+        break;
+        case 'domainadmin':
+          if ($_SESSION['mailcow_cc_role'] != 'admin') {
+            $_SESSION['return'][] = array(
+              'type' => 'danger',
+              'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+              'msg' => 'access_denied'
+            );
+            return false;
+          }
+          if (!is_array($_data['username'])) {
+            $usernames = array();
+            $usernames[] = $_data['username'];
+          }
+          else {
+            $usernames = $_data['username'];
+          }
+          foreach ($usernames as $username) {
+            // Cast to array for single selections
+            $acls = (array)$_data['da_acl'];
+            // Create associative array from index array
+            // All set items are given 1 as value
+            foreach ($acls as $acl_key => $acl_val) {
+              $acl_post[$acl_val] = 1;
+            }
+            // Users cannot change their own ACL
+            if ($_SESSION['mailcow_cc_role'] != 'admin') {
+              $_SESSION['return'][] = array(
+                'type' => 'danger',
+                'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+                'msg' => 'access_denied'
+              );
+              continue;
+            }
+            // Read all available acl options by calling acl(get)
+            // Set all available acl options we cannot find in the post data to 0, else 1
+            $is_now = acl('get', 'domainadmin', $username);
+            if (!empty($is_now)) {
+              foreach ($is_now as $acl_now_name => $acl_now_val) {
+                $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0;
+              }
+            }
+            else {
+              $_SESSION['return'][] = array(
+                'type' => 'danger',
+                'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+                'msg' => 'Cannot determine current ACL'
+              );
+              continue;
+            }
+            foreach ($set_acls as $set_acl_key => $set_acl_val) {
+              $stmt = $pdo->prepare("UPDATE `da_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . "
+                WHERE `username` = :username");
+              $stmt->execute(array(
+                ':username' => $username,
+              ));
+            }
+            $_SESSION['return'][] = array(
+              'type' => 'success',
+              'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+              'msg' => array('acl_saved', $username)
+            );
+          }
+        break;
+      }
+    break;
+    case 'get':
+      switch ($_scope) {
+        case 'user':
+          if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
+            return false;
+          }
+          $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username");
+          $stmt->execute(array(':username' => $_data));
+          $data = $stmt->fetch(PDO::FETCH_ASSOC);
+          if (!empty($data)) {
+            unset($data['username']);
+            return $data;
+          }
+          else {
+            return false;
+          }
+        break;
+        case 'domainadmin':
+          if ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin') {
+            return false;
+          }
+          if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $_SESSION['mailcow_cc_username'] != $_data) {
+            return false;
+          }
+          $stmt = $pdo->prepare("SELECT * FROM `da_acl` WHERE `username` = :username");
+          $stmt->execute(array(':username' => $_data));
+          $data = $stmt->fetch(PDO::FETCH_ASSOC);
+          if (!empty($data)) {
+            unset($data['username']);
+            return $data;
+          }
+          else {
+            return false;
+          }
+        break;
+      }
+    break;
+    case 'to_session':
+      if (!isset($_SESSION['mailcow_cc_role'])) {
+        return false;
+      }
+      unset($_SESSION['acl']);
+      $username = strtolower(trim($_SESSION['mailcow_cc_username']));
+      // Admins get access to all modules
+      if ($_SESSION['mailcow_cc_role'] == 'admin' ||
+        (isset($_SESSION["dual-login"]["role"]) && $_SESSION["dual-login"]["role"] == 'admin')) {
+        $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';");
+        $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC);
+        while ($row = array_shift($acl_all)) {
+          $acl['acl'][$row['Field']] = 1;
+        }
+        $stmt = $pdo->query("SHOW COLUMNS FROM `da_acl` WHERE `Field` != 'username';");
+        $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC);
+        while ($row = array_shift($acl_all)) {
+          $acl['acl'][$row['Field']] = 1;
+        }
+      }
+      elseif ($_SESSION['mailcow_cc_role'] == 'domainadmin' ||
+        (isset($_SESSION["dual-login"]["role"]) && $_SESSION["dual-login"]["role"] == 'domainadmin')) {
+        // Read all exting user_acl modules and set to 1
+        $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';");
+        $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC);
+        while ($row = array_shift($acl_all)) {
+          $acl['acl'][$row['Field']] = 1;
+        }
+        // Read da_acl rules for current user, OVERWRITE overlapping modules
+        // This prevents access to a users sync jobs, when a domain admins was not given access to sync jobs
+        $stmt = $pdo->prepare("SELECT * FROM `da_acl` WHERE `username` = :username");
+        $stmt->execute(array(':username' => (isset($_SESSION["dual-login"]["username"])) ? $_SESSION["dual-login"]["username"] : $username));
+        $acl_user = $stmt->fetch(PDO::FETCH_ASSOC);
+        foreach ($acl_user as $acl_user_key => $acl_user_val) {
+          $acl['acl'][$acl_user_key] = $acl_user_val;
+        }
+        unset($acl['acl']['username']);
+      }
+      elseif ($_SESSION['mailcow_cc_role'] == 'user') {
+        $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username");
+        $stmt->execute(array(':username' => $username));
+        $acl['acl'] = $stmt->fetch(PDO::FETCH_ASSOC);
+        unset($acl['acl']['username']);
+      }
+      if (!empty($acl)) {
+        $_SESSION = array_merge($_SESSION, $acl);
+      }
+    break;
+  }
+}

+ 32 - 0
data/web/inc/functions.address_rewriting.inc.php

@@ -233,6 +233,14 @@ function bcc($_action, $_data = null, $attr = null) {
       return $bccdata;
       return $bccdata;
     break;
     break;
     case 'delete':
     case 'delete':
+      if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data, $_attr),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
       $ids = (array)$_data['id'];
       $ids = (array)$_data['id'];
       foreach ($ids as $id) {
       foreach ($ids as $id) {
         if (!is_numeric($id)) {
         if (!is_numeric($id)) {
@@ -279,6 +287,14 @@ function recipient_map($_action, $_data = null, $attr = null) {
   }
   }
   switch ($_action) {
   switch ($_action) {
     case 'add':
     case 'add':
+      if (!isset($_SESSION['acl']['recipient_maps']) || $_SESSION['acl']['recipient_maps'] != "1" ) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data, $_attr),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
       $old_dest = strtolower(trim($_data['recipient_map_old']));
       $old_dest = strtolower(trim($_data['recipient_map_old']));
       if (substr($old_dest, 0, 1) == '@') {
       if (substr($old_dest, 0, 1) == '@') {
         $old_dest = substr($old_dest, 1);
         $old_dest = substr($old_dest, 1);
@@ -343,6 +359,14 @@ function recipient_map($_action, $_data = null, $attr = null) {
       );
       );
     break;
     break;
     case 'edit':
     case 'edit':
+      if (!isset($_SESSION['acl']['recipient_maps']) || $_SESSION['acl']['recipient_maps'] != "1" ) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data, $_attr),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
       $ids = (array)$_data['id'];
       $ids = (array)$_data['id'];
       foreach ($ids as $id) {
       foreach ($ids as $id) {
         $is_now = recipient_map('details', $id);
         $is_now = recipient_map('details', $id);
@@ -458,6 +482,14 @@ function recipient_map($_action, $_data = null, $attr = null) {
       return $mapdata;
       return $mapdata;
     break;
     break;
     case 'delete':
     case 'delete':
+      if (!isset($_SESSION['acl']['recipient_maps']) || $_SESSION['acl']['recipient_maps'] != "1" ) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data, $_attr),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
       $ids = (array)$_data['id'];
       $ids = (array)$_data['id'];
       foreach ($ids as $id) {
       foreach ($ids as $id) {
         if (!is_numeric($id)) {
         if (!is_numeric($id)) {

+ 65 - 143
data/web/inc/functions.domain_admin.inc.php

@@ -90,43 +90,22 @@ function domain_admin($_action, $_data = null) {
             );
             );
             return false;
             return false;
           }
           }
-          try {
-            $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
-                VALUES (:username, :domain, :created, :active)");
-            $stmt->execute(array(
-              ':username' => $username,
-              ':domain' => $domain,
-              ':created' => date('Y-m-d H:i:s'),
-              ':active' => $active
-            ));
-          }
-          catch (PDOException $e) {
-            domain_admin('delete', $username);
-            $_SESSION['return'][] = array(
-              'type' => 'danger',
-              'log' => array(__FUNCTION__, $_action, $_data_log),
-              'msg' => array('mysql_error', $e)
-            );
-            return false;
-          }
-        }
-        try {
-          $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
-            VALUES (:username, :password_hashed, '0', :active)");
+          $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
+              VALUES (:username, :domain, :created, :active)");
           $stmt->execute(array(
           $stmt->execute(array(
             ':username' => $username,
             ':username' => $username,
-            ':password_hashed' => $password_hashed,
+            ':domain' => $domain,
+            ':created' => date('Y-m-d H:i:s'),
             ':active' => $active
             ':active' => $active
           ));
           ));
         }
         }
-        catch (PDOException $e) {
-          $_SESSION['return'][] = array(
-            'type' => 'danger',
-            'log' => array(__FUNCTION__, $_action, $_data_log),
-            'msg' => array('mysql_error', $e)
-          );
-          return false;
-        }
+        $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
+          VALUES (:username, :password_hashed, '0', :active)");
+        $stmt->execute(array(
+          ':username' => $username,
+          ':password_hashed' => $password_hashed,
+          ':active' => $active
+        ));
       }
       }
       else {
       else {
         $_SESSION['return'][] = array(
         $_SESSION['return'][] = array(
@@ -136,6 +115,10 @@ function domain_admin($_action, $_data = null) {
         );
         );
         return false;
         return false;
       }
       }
+      $stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)");
+      $stmt->execute(array(
+        ':username' => $username
+      ));
       $_SESSION['return'][] = array(
       $_SESSION['return'][] = array(
         'type' => 'success',
         'type' => 'success',
         'log' => array(__FUNCTION__, $_action, $_data_log),
         'log' => array(__FUNCTION__, $_action, $_data_log),
@@ -209,41 +192,20 @@ function domain_admin($_action, $_data = null) {
               continue;
               continue;
             }
             }
           }
           }
-          try {
-            $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
-            $stmt->execute(array(
-              ':username' => $username,
-            ));
-          }
-          catch (PDOException $e) {
-            $_SESSION['return'][] = array(
-              'type' => 'danger',
-              'log' => array(__FUNCTION__, $_action, $_data_log),
-              'msg' => array('mysql_error', $e)
-            );
-            continue;
-          }
-
+          $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
+          $stmt->execute(array(
+            ':username' => $username,
+          ));
           if (!empty($domains)) {
           if (!empty($domains)) {
             foreach ($domains as $domain) {
             foreach ($domains as $domain) {
-              try {
-                $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
-                  VALUES (:username_new, :domain, :created, :active)");
-                $stmt->execute(array(
-                  ':username_new' => $username_new,
-                  ':domain' => $domain,
-                  ':created' => date('Y-m-d H:i:s'),
-                  ':active' => $active
-                ));
-              }
-              catch (PDOException $e) {
-                $_SESSION['return'][] = array(
-                  'type' => 'danger',
-                  'log' => array(__FUNCTION__, $_action, $_data_log),
-                  'msg' => array('mysql_error', $e)
-                );
-                continue;
-              }
+              $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
+                VALUES (:username_new, :domain, :created, :active)");
+              $stmt->execute(array(
+                ':username_new' => $username_new,
+                ':domain' => $domain,
+                ':created' => date('Y-m-d H:i:s'),
+                ':active' => $active
+              ));
             }
             }
           }
           }
 
 
@@ -265,56 +227,36 @@ function domain_admin($_action, $_data = null) {
               continue;
               continue;
             }
             }
             $password_hashed = hash_password($password);
             $password_hashed = hash_password($password);
-            try {
-              $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
-              $stmt->execute(array(
-                ':password_hashed' => $password_hashed,
-                ':username_new' => $username_new,
-                ':username' => $username,
-                ':active' => $active
-              ));
-              if (isset($_data['disable_tfa'])) {
-                $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
-                $stmt->execute(array(':username' => $username));
-              }
-              else {
-                $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
-                $stmt->execute(array(':username_new' => $username_new, ':username' => $username));
-              }
+            $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
+            $stmt->execute(array(
+              ':password_hashed' => $password_hashed,
+              ':username_new' => $username_new,
+              ':username' => $username,
+              ':active' => $active
+            ));
+            if (isset($_data['disable_tfa'])) {
+              $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
+              $stmt->execute(array(':username' => $username));
             }
             }
-            catch (PDOException $e) {
-              $_SESSION['return'][] = array(
-                'type' => 'danger',
-                'log' => array(__FUNCTION__, $_action, $_data_log),
-                'msg' => array('mysql_error', $e)
-              );
-              continue;
+            else {
+              $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
+              $stmt->execute(array(':username_new' => $username_new, ':username' => $username));
             }
             }
           }
           }
           else {
           else {
-            try {
-              $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
-              $stmt->execute(array(
-                ':username_new' => $username_new,
-                ':username' => $username,
-                ':active' => $active
-              ));
-              if (isset($_data['disable_tfa'])) {
-                $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
-                $stmt->execute(array(':username' => $username));
-              }
-              else {
-                $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
-                $stmt->execute(array(':username_new' => $username_new, ':username' => $username));
-              }
+            $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
+            $stmt->execute(array(
+              ':username_new' => $username_new,
+              ':username' => $username,
+              ':active' => $active
+            ));
+            if (isset($_data['disable_tfa'])) {
+              $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
+              $stmt->execute(array(':username' => $username));
             }
             }
-            catch (PDOException $e) {
-              $_SESSION['return'][] = array(
-                'type' => 'danger',
-                'log' => array(__FUNCTION__, $_action, $_data_log),
-                'msg' => array('mysql_error', $e)
-              );
-              continue;
+            else {
+              $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
+              $stmt->execute(array(':username_new' => $username_new, ':username' => $username));
             }
             }
           }
           }
           $_SESSION['return'][] = array(
           $_SESSION['return'][] = array(
@@ -365,21 +307,11 @@ function domain_admin($_action, $_data = null) {
             return false;
             return false;
           }
           }
           $password_hashed = hash_password($password_new);
           $password_hashed = hash_password($password_new);
-          try {
-            $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
-            $stmt->execute(array(
-              ':password_hashed' => $password_hashed,
-              ':username' => $username
-            ));
-          }
-          catch (PDOException $e) {
-            $_SESSION['return'][] = array(
-              'type' => 'danger',
-              'log' => array(__FUNCTION__, $_action, $_data_log),
-              'msg' => array('mysql_error', $e)
-            );
-            return false;
-          }
+          $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
+          $stmt->execute(array(
+            ':password_hashed' => $password_hashed,
+            ':username' => $username
+          ));
         }
         }
         $_SESSION['return'][] = array(
         $_SESSION['return'][] = array(
           'type' => 'success',
           'type' => 'success',
@@ -407,24 +339,14 @@ function domain_admin($_action, $_data = null) {
           );
           );
           continue;
           continue;
         }
         }
-        try {
-          $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
-          $stmt->execute(array(
-            ':username' => $username,
-          ));
-          $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
-          $stmt->execute(array(
-            ':username' => $username,
-          ));
-        }
-        catch (PDOException $e) {
-          $_SESSION['return'][] = array(
-            'type' => 'danger',
-            'log' => array(__FUNCTION__, $_action, $_data_log),
-            'msg' => array('mysql_error', $e)
-          );
-          continue;
-        }
+        $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
+        $stmt->execute(array(
+          ':username' => $username,
+        ));
+        $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
+        $stmt->execute(array(
+          ':username' => $username,
+        ));
         $_SESSION['return'][] = array(
         $_SESSION['return'][] = array(
           'type' => 'success',
           'type' => 'success',
           'log' => array(__FUNCTION__, $_action, $_data_log),
           'log' => array(__FUNCTION__, $_action, $_data_log),

+ 1 - 49
data/web/inc/functions.inc.php

@@ -373,54 +373,6 @@ function check_login($user, $pass) {
 	sleep($_SESSION['ldelay']);
 	sleep($_SESSION['ldelay']);
   return false;
   return false;
 }
 }
-function set_acl() {
-	global $pdo;
-	if (!isset($_SESSION['mailcow_cc_username'])) {
-		return false;
-	}
-	if ($_SESSION['mailcow_cc_role'] == 'admin' || $_SESSION['mailcow_cc_role'] == 'domainadmin') {
-    $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';");
-    $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC);
-    while ($row = array_shift($acl_all)) {
-      $acl['acl'][$row['Field']] = 1;
-    }
-	}
-  else {
-    $username = strtolower(trim($_SESSION['mailcow_cc_username']));
-    $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username");
-    $stmt->execute(array(':username' => $username));
-    $acl['acl'] = $stmt->fetch(PDO::FETCH_ASSOC);
-    unset($acl['acl']['username']);
-  }
-  if (!empty($acl)) {
-    $_SESSION = array_merge($_SESSION, $acl);
-  }
-  else {
-    $_SESSION['return'][] =  array(
-      'type' => 'info',
-      'log' => array(__FUNCTION__),
-      'msg' => 'set_acl_failed'
-    );
-    return false;
-  }
-}
-function get_acl($username) {
-	global $pdo;
-	if ($_SESSION['mailcow_cc_role'] != "admin") {
-		return false;
-	}
-  $username = strtolower(trim($username));
-  $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username");
-  $stmt->execute(array(':username' => $username));
-  $acl = $stmt->fetch(PDO::FETCH_ASSOC);
-  unset($acl['username']);
-  if (!empty($acl)) {
-    return $acl;
-  }
-  else {
-    return false;
-  }
-}
 function formatBytes($size, $precision = 2) {
 function formatBytes($size, $precision = 2) {
 	if(!is_numeric($size)) {
 	if(!is_numeric($size)) {
 		return "0";
 		return "0";
@@ -1266,7 +1218,7 @@ function get_admin_details() {
     return false;
     return false;
   }
   }
   $stmt = $pdo->query("SELECT `admin`.`username`, `api`.`active` AS `api_active`, `api`.`api_key`, `api`.`allow_from` FROM `admin`
   $stmt = $pdo->query("SELECT `admin`.`username`, `api`.`active` AS `api_active`, `api`.`api_key`, `api`.`allow_from` FROM `admin`
-    INNER JOIN `api` ON `admin`.`username` = `api`.`username`
+    LEFT OUTER JOIN `api` ON `admin`.`username` = `api`.`username`
     WHERE `admin`.`superadmin`='1'
     WHERE `admin`.`superadmin`='1'
       AND `admin`.`active`='1'");
       AND `admin`.`active`='1'");
   $data = $stmt->fetch(PDO::FETCH_ASSOC);
   $data = $stmt->fetch(PDO::FETCH_ASSOC);

+ 8 - 9
data/web/inc/functions.mailbox.inc.php

@@ -139,7 +139,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             $stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_name` = 'inactive' WHERE `username` = :username AND `filter_type` = :filter_type");
             $stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_name` = 'inactive' WHERE `username` = :username AND `filter_type` = :filter_type");
             $stmt->execute(array(
             $stmt->execute(array(
               ':username' => $username,
               ':username' => $username,
-              'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
               ':filter_type' => $filter_type
               ':filter_type' => $filter_type
             ));
             ));
           }
           }
@@ -1453,14 +1452,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
           }
           }
         break;
         break;
         case 'filter':
         case 'filter':
-          $sieve = new Sieve\SieveParser();
-          if (!is_array($_data['id'])) {
-            $ids = array();
-            $ids[] = $_data['id'];
-          }
-          else {
-            $ids = $_data['id'];
-          }
           if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
           if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
             $_SESSION['return'][] = array(
             $_SESSION['return'][] = array(
               'type' => 'danger',
               'type' => 'danger',
@@ -1469,6 +1460,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             );
             );
             return false;
             return false;
           }
           }
+          $sieve = new Sieve\SieveParser();
+          if (!is_array($_data['id'])) {
+            $ids = array();
+            $ids[] = $_data['id'];
+          }
+          else {
+            $ids = $_data['id'];
+          }
           foreach ($ids as $id) {
           foreach ($ids as $id) {
             $is_now = mailbox('get', 'filter_details', $id);
             $is_now = mailbox('get', 'filter_details', $id);
             if (!empty($is_now)) {
             if (!empty($is_now)) {

+ 16 - 16
data/web/inc/functions.policy.inc.php

@@ -6,6 +6,14 @@ function policy($_action, $_scope, $_data = null) {
 	$_data_log = $_data;
 	$_data_log = $_data;
   switch ($_action) {
   switch ($_action) {
     case 'add':
     case 'add':
+      if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
       switch ($_scope) {
       switch ($_scope) {
         case 'domain':
         case 'domain':
           $object = $_data['domain'];
           $object = $_data['domain'];
@@ -90,14 +98,6 @@ function policy($_action, $_scope, $_data = null) {
             );
             );
             return false;
             return false;
           }
           }
-          if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) {
-            $_SESSION['return'][] = array(
-              'type' => 'danger',
-              'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
-              'msg' => 'access_denied'
-            );
-            return false;
-          }
           if ($_data['object_list'] == "bl") {
           if ($_data['object_list'] == "bl") {
             $object_list = "blacklist_from";
             $object_list = "blacklist_from";
           }
           }
@@ -151,6 +151,14 @@ function policy($_action, $_scope, $_data = null) {
       }
       }
     break;
     break;
     case 'delete':
     case 'delete':
+      if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
       switch ($_scope) {
       switch ($_scope) {
         case 'domain':
         case 'domain':
           (array)$prefids = $_data['prefid'];
           (array)$prefids = $_data['prefid'];
@@ -215,14 +223,6 @@ function policy($_action, $_scope, $_data = null) {
           else {
           else {
             $prefids = $_data['prefid'];
             $prefids = $_data['prefid'];
           }
           }
-          if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) {
-            $_SESSION['return'][] = array(
-              'type' => 'danger',
-              'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
-              'msg' => 'access_denied'
-            );
-            return false;
-          }
           foreach ($prefids as $prefid) {
           foreach ($prefids as $prefid) {
             if (!is_numeric($prefid)) {
             if (!is_numeric($prefid)) {
               $_SESSION['return'][] = array(
               $_SESSION['return'][] = array(

+ 8 - 0
data/web/inc/functions.ratelimit.inc.php

@@ -5,6 +5,14 @@ function ratelimit($_action, $_scope, $_data = null) {
   $_data_log = $_data;
   $_data_log = $_data;
   switch ($_action) {
   switch ($_action) {
     case 'edit':
     case 'edit':
+      if (!isset($_SESSION['acl']['ratelimit']) || $_SESSION['acl']['ratelimit'] != "1" ) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
       switch ($_scope) {
       switch ($_scope) {
         case 'domain':
         case 'domain':
           if (!is_array($_data['object'])) {
           if (!is_array($_data['object'])) {

+ 29 - 5
data/web/inc/init_db.inc.php

@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
   try {
     global $pdo;
     global $pdo;
 
 
-    $db_version = "19082018_1004";
+    $db_version = "01092018_1902";
 
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -280,10 +280,7 @@ function init_db_schema() {
           "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
-          "filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
-          "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'",
-          "recipient_maps" => "TINYINT(1) NOT NULL DEFAULT '0'",
           ),
           ),
         "keys" => array(
         "keys" => array(
           "primary" => array(
           "primary" => array(
@@ -300,6 +297,32 @@ function init_db_schema() {
         ),
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
       ),
+      "da_acl" => array(
+        "cols" => array(
+          "username" => "VARCHAR(255) NOT NULL",
+          "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "login_as" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          ),
+        "keys" => array(
+          "primary" => array(
+            "" => array("username")
+          ),
+          "fkey" => array(
+            "fk_domain_admin_acl" => array(
+              "col" => "username",
+              "ref" => "domain_admins.username",
+              "delete" => "CASCADE",
+              "update" => "NO ACTION"
+            )
+          )
+        ),
+        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+      ),
       "alias_domain" => array(
       "alias_domain" => array(
         "cols" => array(
         "cols" => array(
           "alias_domain" => "VARCHAR(255) NOT NULL",
           "alias_domain" => "VARCHAR(255) NOT NULL",
@@ -950,8 +973,9 @@ DELIMITER ;';
       'msg' => 'db_init_complete'
       'msg' => 'db_init_complete'
     );
     );
 
 
-    // Fix user_acl
+    // Fix ACL
     $stmt = $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);");
     $stmt = $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);");
+    $stmt = $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);");
   }
   }
   catch (PDOException $e) {
   catch (PDOException $e) {
     $_SESSION['return'][] = array(
     $_SESSION['return'][] = array(

+ 2 - 1
data/web/inc/prerequisites.inc.php

@@ -124,6 +124,7 @@ if (isset($_GET['lang']) && in_array($_GET['lang'], $AVAILABLE_LANGUAGES)) {
 require_once $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.php';
 include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.php';
 include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php';
@@ -141,6 +142,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php';
 init_db_schema();
 init_db_schema();
 if (isset($_SESSION['mailcow_cc_role'])) {
 if (isset($_SESSION['mailcow_cc_role'])) {
-  set_acl();
+  acl('to_session');
 }
 }
 $UI_TEXTS = customize('get', 'ui_texts');
 $UI_TEXTS = customize('get', 'ui_texts');

+ 1 - 1
data/web/inc/triggers.inc.php

@@ -40,7 +40,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
 	}
 	}
 }
 }
 
 
-if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['acl']['login_as'] == "1") {
 	if (isset($_GET["duallogin"])) {
 	if (isset($_GET["duallogin"])) {
     $duallogin = html_entity_decode(rawurldecode($_GET["duallogin"]));
     $duallogin = html_entity_decode(rawurldecode($_GET["duallogin"]));
     if (filter_var($duallogin, FILTER_VALIDATE_EMAIL)) {
     if (filter_var($duallogin, FILTER_VALIDATE_EMAIL)) {

+ 4 - 0
data/web/js/admin.js

@@ -13,6 +13,10 @@ jQuery(function($){
     e.preventDefault();
     e.preventDefault();
     $('#duplicate_dkim_arrow').toggleClass("animation"); 
     $('#duplicate_dkim_arrow').toggleClass("animation"); 
   });
   });
+  $("#api_legend").on('click', function(e) {
+    e.preventDefault();
+    $('#api_arrow').toggleClass("animation"); 
+  });
   $("#rspamd_preset_1").on('click', function(e) {
   $("#rspamd_preset_1").on('click', function(e) {
     e.preventDefault();
     e.preventDefault();
     $("form[data-id=rsetting]").find("#desc").val(lang.rsettings_preset_1);
     $("form[data-id=rsetting]").find("#desc").val(lang.rsettings_preset_1);

+ 2 - 2
data/web/js/api.js

@@ -5,9 +5,9 @@ $(document).ready(function() {
     } else {
     } else {
       var parent_btn_grp = $(elem).parentsUntil(".btn-group").parent();
       var parent_btn_grp = $(elem).parentsUntil(".btn-group").parent();
       if (parent_btn_grp.hasClass('btn-group')) {
       if (parent_btn_grp.hasClass('btn-group')) {
-        parent_btn_grp.replaceWith('<button class="btn btn-default btn-sm" disabled>' + loading_text + '</a>');
+        parent_btn_grp.replaceWith('<button class="btn btn-default btn-sm" disabled>' + lang_footer.loading + '</a>');
       }
       }
-      $(elem).text(loading_text);
+      $(elem).text(lang_footer.loading);
       $(elem).attr('data-submitted', '1');
       $(elem).attr('data-submitted', '1');
       function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); };
       function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); };
       $(document).on("keydown", disableF5);
       $(document).on("keydown", disableF5);

+ 6 - 6
data/web/js/edit.js

@@ -61,10 +61,10 @@ jQuery(function($){
       "columns": [
       "columns": [
         {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
         {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
         {"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false},
         {"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false},
-        {"sorted": true,"name":"value","title":lang.spamfilter_table_rule},
+        {"sorted": true,"name":"value","title":lang_user.spamfilter_table_rule},
         {"name":"object","title":"Scope"}
         {"name":"object","title":"Scope"}
       ],
       ],
-      "empty": lang.empty,
+      "empty": lang_user.empty,
       "rows": $.ajax({
       "rows": $.ajax({
         dataType: 'json',
         dataType: 'json',
         url: '/api/v1/get/policy_wl_domain/' + table_for_domain,
         url: '/api/v1/get/policy_wl_domain/' + table_for_domain,
@@ -78,7 +78,7 @@ jQuery(function($){
               item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />';
               item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />';
             }
             }
             else {
             else {
-              item.chkbox = '<input type="checkbox" disabled title="' + lang.spamfilter_table_domain_policy + '" />';
+              item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
             }
             }
           });
           });
         }
         }
@@ -98,10 +98,10 @@ jQuery(function($){
       "columns": [
       "columns": [
         {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
         {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
         {"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false},
         {"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false},
-        {"sorted": true,"name":"value","title":lang.spamfilter_table_rule},
+        {"sorted": true,"name":"value","title":lang_user.spamfilter_table_rule},
         {"name":"object","title":"Scope"}
         {"name":"object","title":"Scope"}
       ],
       ],
-      "empty": lang.empty,
+      "empty": lang_user.empty,
       "rows": $.ajax({
       "rows": $.ajax({
         dataType: 'json',
         dataType: 'json',
         url: '/api/v1/get/policy_bl_domain/' + table_for_domain,
         url: '/api/v1/get/policy_bl_domain/' + table_for_domain,
@@ -115,7 +115,7 @@ jQuery(function($){
               item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
               item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
             }
             }
             else {
             else {
-              item.chkbox = '<input type="checkbox" disabled tooltip="' + lang.spamfilter_table_domain_policy + '" />';
+              item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
             }
             }
           });
           });
         }
         }

+ 14 - 5
data/web/js/mailbox.js

@@ -1,4 +1,5 @@
 $(document).ready(function() {
 $(document).ready(function() {
+  acl_data = JSON.parse(acl);
   FooTable.domainFilter = FooTable.Filtering.extend({
   FooTable.domainFilter = FooTable.Filtering.extend({
     construct: function(instance){
     construct: function(instance){
       this._super(instance);
       this._super(instance);
@@ -82,6 +83,7 @@ $(document).ready(function() {
 
 
   $(".generate_password").click(function( event ) {
   $(".generate_password").click(function( event ) {
     event.preventDefault();
     event.preventDefault();
+    $('[data-hibp]').trigger('input');
     var random_passwd = Math.random().toString(36).slice(-8)
     var random_passwd = Math.random().toString(36).slice(-8)
     $('#password').prop('type', 'text');
     $('#password').prop('type', 'text');
     $('#password').val(random_passwd);
     $('#password').val(random_passwd);
@@ -233,6 +235,13 @@ jQuery(function($){
     eval(draw_table + '()');
     eval(draw_table + '()');
   });
   });
   function table_mailbox_ready(ft, name) {
   function table_mailbox_ready(ft, name) {
+    if(is_dual) {
+      $('.login_as').data("toggle", "tooltip")
+        .attr("disabled", true)
+        .removeAttr("href")
+        .attr("title", "Dual login cannot be used twice")
+        .tooltip();
+    }
     heading = ft.$el.parents('.tab-pane').find('.panel-heading')
     heading = ft.$el.parents('.tab-pane').find('.panel-heading')
     var ft_paging = ft.use(FooTable.Paging)
     var ft_paging = ft.use(FooTable.Paging)
     $(heading).children('.table-lines').text(function(){
     $(heading).children('.table-lines').text(function(){
@@ -264,10 +273,10 @@ jQuery(function($){
         },
         },
         },
         },
         {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm","style":{"width":"125px"}},
         {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm","style":{"width":"125px"}},
-        {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"width":"125px"}},
-        {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm"},
+        {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"maxWidth":"100px","width":"100px"}},
+        {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm md"},
         {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
         {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
-        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
+        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
       ],
       ],
       "rows": $.ajax({
       "rows": $.ajax({
         dataType: 'json',
         dataType: 'json',
@@ -374,11 +383,11 @@ jQuery(function($){
               }).join('/1');
               }).join('/1');
             }
             }
             item.chkbox = '<input type="checkbox" data-id="mailbox" name="multi_select" value="' + encodeURIComponent(item.username) + '" />';
             item.chkbox = '<input type="checkbox" data-id="mailbox" name="multi_select" value="' + encodeURIComponent(item.username) + '" />';
-            if (role == "admin") {
+            if (acl_data.login_as === 1) {
             item.action = '<div class="btn-group">' +
             item.action = '<div class="btn-group">' +
               '<a href="/edit.php?mailbox=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
               '<a href="/edit.php?mailbox=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
               '<a href="#" id="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
               '<a href="#" id="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
-              '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-success"><span class="glyphicon glyphicon-user"></span> Login</a>' +
+              '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="login_as btn btn-xs btn-success"><span class="glyphicon glyphicon-user"></span> Login</a>' +
               '</div>';
               '</div>';
             }
             }
             else {
             else {

+ 193 - 0
data/web/js/mailcow.js

@@ -0,0 +1,193 @@
+$(document).ready(function() {
+  // mailcow alert box generator
+  window.mailcow_alert_box = function(message, type) {
+    msg = $('<span/>').text(message).text();
+    if (type == 'danger') {
+      auto_hide = 0;
+      $('#' + localStorage.getItem("add_modal")).modal('show');
+      localStorage.removeItem("add_modal");
+    } else {
+      auto_hide = 5000;
+    }
+    $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
+  }
+
+  // https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate
+  function shake(div,interval=100,distance=10,times=4) {
+    $(div).css('position','relative');
+    for(var iter=0;iter<(times+1);iter++){
+      $(div).animate({ left: ((iter%2==0 ? distance : distance*-1))}, interval);
+    }
+    $(div).animate({ left: 0},interval);
+  }
+
+  // form cache
+  $('[data-cached-form="true"]').formcache({key: $(this).data('id')});
+
+  //  tooltips
+  $(function () {
+    $('[data-toggle="tooltip"]').tooltip()
+  });
+
+  // remember last navigation pill
+  (function () {
+    'use strict';
+    if ($('a[data-toggle="tab"]').length) {
+      $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+        if ($(this).data('dont-remember') == 1) {
+          return true;
+        }
+        var id = $(this).parents('[role="tablist"]').attr('id');
+        var key = 'lastTag';
+        if (id) {
+          key += ':' + id;
+        }
+        localStorage.setItem(key, $(e.target).attr('href'));
+      });
+      $('[role="tablist"]').each(function (idx, elem) {
+        var id = $(elem).attr('id');
+        var key = 'lastTag';
+        if (id) {
+          key += ':' + id;
+        }
+        var lastTab = localStorage.getItem(key);
+        if (lastTab) {
+          $('[href="' + lastTab + '"]').tab('show');
+        }
+      });
+    }
+  })();
+
+  // IE fix to hide scrollbars when table body is empty
+  $('tbody').filter(function (index) {
+    return $(this).children().length < 1;
+  }).remove();
+
+  // selectpicker
+  $('select').selectpicker();
+
+  // haveibeenpwned?
+  $('[data-hibp]').after('<p class="small haveibeenpwned">↪ Check against haveibeenpwned.com</p><span class="hibp-out"></span>');
+  $('[data-hibp]').on('input', function() {
+    out_field = $(this).next('.haveibeenpwned').next('.hibp-out').text('').attr('class', 'hibp-out');
+  });
+  $('.haveibeenpwned:not(.task-running)').on('click', function() {
+    var hibp_field = $(this)
+    $(hibp_field).addClass('task-running');
+    var hibp_result = $(hibp_field).next('.hibp-out')
+    var password_field = $(this).prev('[data-hibp]')
+    if ($(password_field).val() == '') {
+      shake(password_field);
+    }
+    else {
+      $(hibp_result).attr('class', 'hibp-out label label-info');
+      $(hibp_result).text(lang_footer.loading);
+      var password_digest = $.sha1($(password_field).val())
+      var digest_five = password_digest.substring(0, 5).toUpperCase();
+      var queryURL = "https://api.pwnedpasswords.com/range/" + digest_five;
+      var compl_digest = password_digest.substring(5, 41).toUpperCase();
+      $.ajax({
+        url: queryURL,
+        type: 'GET',
+        success: function(res) {
+          if (res.search(compl_digest) > -1){
+            $(hibp_result).removeClass('label label-info').addClass('label label-danger');
+            $(hibp_result).text(lang_footer.hibp_nok)
+          } else {
+            $(hibp_result).removeClass('label label-info').addClass('label label-success');
+            $(hibp_result).text(lang_footer.hibp_ok)
+          }
+          $(hibp_field).removeClass('task-running');
+        },
+        error: function(xhr, status, error) {
+          $(hibp_result).removeClass('label label-info').addClass('label label-warning');
+          $(hibp_result).text('API error: ' + xhr.responseText)
+          $(hibp_field).removeClass('task-running');
+        }
+      });
+    }
+  });
+
+  // Disable disallowed inputs
+  $('[data-acl="0"]').each(function(){
+    if ($(this).attr('class') == 'btn-group') {
+      $(this).find('a').each(function(){
+        $(this).removeClass('dropdown-toggle')
+          .removeAttr('data-toggle')
+          .removeAttr('id')
+          .attr("disabled", true);
+        $(this).click(function(event) {
+          event.preventDefault();
+          return;
+        });
+      });
+      $(this).find('button').each(function() {
+        $(this).attr("disabled", true);
+      });
+    } else if ($(this).attr('class') == 'input-group') {
+      $(this).find('input').each(function() {
+        $(this).removeClass('dropdown-toggle')
+          .removeAttr('data-toggle')
+          .attr("disabled", true);
+        $(this).click(function(event) {
+          event.preventDefault();
+        });
+      });
+      $(this).find('button').each(function() {
+        $(this).attr("disabled", true);
+      });
+    } else if ($(this).hasClass('btn')) {
+      $(this).attr("disabled", true);
+    } else if ($(this).attr('data-provide', 'slider')) {
+      $(this).slider("disable");
+    }
+    $(this).data("toggle", "tooltip");
+    $(this).attr("title", lang_acl.prohibited);
+    $(this).tooltip(); 
+  });
+
+  // disable submit after submitting form (not API driven buttons)
+  $('form').submit(function() {
+    if ($('form button[type="submit"]').data('submitted') == '1') {
+      return false;
+    } else {
+      $(this).find('button[type="submit"]').first().text(lang_footer.loading);
+      $('form button[type="submit"]').attr('data-submitted', '1');
+      function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); };
+      $(document).on("keydown", disableF5);
+    }
+  });
+
+  // trigger container restart
+  $('#RestartContainer').on('show.bs.modal', function(e) {
+    var container = $(e.relatedTarget).data('container');
+    $('#containerName').text(container);
+    $('#triggerRestartContainer').click(function(){
+      $(this).prop("disabled",true);
+      $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> ');
+      $('#statusTriggerRestartContainer').html(lang_footer.restarting_container);
+      $.ajax({
+        method: 'get',
+        url: '/inc/ajax/container_ctrl.php',
+        timeout: docker_timeout,
+        data: {
+        'service': container,
+        'action': 'restart'
+        }
+      })
+      .always( function (data, status) {
+        $('#statusTriggerRestartContainer').append(data);
+        var htmlResponse = $.parseHTML(data)
+        if ($(htmlResponse).find('span').hasClass('text-success')) {
+          $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-ok"></span> ');
+          setTimeout(function(){
+            $('#RestartContainer').modal('toggle'); 
+            window.location = window.location.href.split("#")[0];
+          }, 1200);
+        } else {
+          $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-remove"></span> ');
+        }
+      })
+    });
+  })
+});

+ 1 - 0
data/web/js/sha1.min.js

@@ -0,0 +1 @@
+!function(r){var o=function(r,o){return r<<o|r>>>32-o},e=function(r){var o,e="";for(o=7;o>=0;o--)e+=(r>>>4*o&15).toString(16);return e};jQuery.extend({sha1:function(r){var a,t,n,h,C,c,f,d,u,i=new Array(80),A=1732584193,g=4023233417,s=2562383102,S=271733878,m=3285377520,p=(r=function(r){r=r.replace(/\x0d\x0a/g,"\n");for(var o="",e=0;e<r.length;e++){var a=r.charCodeAt(e);a<128?o+=String.fromCharCode(a):a>127&&a<2048?(o+=String.fromCharCode(a>>6|192),o+=String.fromCharCode(63&a|128)):(o+=String.fromCharCode(a>>12|224),o+=String.fromCharCode(a>>6&63|128),o+=String.fromCharCode(63&a|128))}return o}(r)).length,l=new Array;for(t=0;t<p-3;t+=4)n=r.charCodeAt(t)<<24|r.charCodeAt(t+1)<<16|r.charCodeAt(t+2)<<8|r.charCodeAt(t+3),l.push(n);switch(p%4){case 0:t=2147483648;break;case 1:t=r.charCodeAt(p-1)<<24|8388608;break;case 2:t=r.charCodeAt(p-2)<<24|r.charCodeAt(p-1)<<16|32768;break;case 3:t=r.charCodeAt(p-3)<<24|r.charCodeAt(p-2)<<16|r.charCodeAt(p-1)<<8|128}for(l.push(t);l.length%16!=14;)l.push(0);for(l.push(p>>>29),l.push(p<<3&4294967295),a=0;a<l.length;a+=16){for(t=0;t<16;t++)i[t]=l[a+t];for(t=16;t<=79;t++)i[t]=o(i[t-3]^i[t-8]^i[t-14]^i[t-16],1);for(h=A,C=g,c=s,f=S,d=m,t=0;t<=19;t++)u=o(h,5)+(C&c|~C&f)+d+i[t]+1518500249&4294967295,d=f,f=c,c=o(C,30),C=h,h=u;for(t=20;t<=39;t++)u=o(h,5)+(C^c^f)+d+i[t]+1859775393&4294967295,d=f,f=c,c=o(C,30),C=h,h=u;for(t=40;t<=59;t++)u=o(h,5)+(C&c|C&f|c&f)+d+i[t]+2400959708&4294967295,d=f,f=c,c=o(C,30),C=h,h=u;for(t=60;t<=79;t++)u=o(h,5)+(C^c^f)+d+i[t]+3395469782&4294967295,d=f,f=c,c=o(C,30),C=h,h=u;A=A+h&4294967295,g=g+C&4294967295,s=s+c&4294967295,S=S+f&4294967295,m=m+d&4294967295}return(u=e(A)+e(g)+e(s)+e(S)+e(m)).toLowerCase()}})}();

+ 6 - 0
data/web/json_api.php

@@ -1039,6 +1039,12 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "rl-mbox":
           case "rl-mbox":
             process_edit_return(ratelimit('edit', 'mailbox', array_merge(array('object' => $items), $attr)));
             process_edit_return(ratelimit('edit', 'mailbox', array_merge(array('object' => $items), $attr)));
           break;
           break;
+          case "user-acl":
+            process_edit_return(acl('edit', 'user', array_merge(array('username' => $items), $attr)));
+          break;
+          case "da-acl":
+            process_edit_return(acl('edit', 'domainadmin', array_merge(array('username' => $items), $attr)));
+          break;
           case "alias-domain":
           case "alias-domain":
             process_edit_return(mailbox('edit', 'alias_domain', array_merge(array('alias_domain' => $items), $attr)));
             process_edit_return(mailbox('edit', 'alias_domain', array_merge(array('alias_domain' => $items), $attr)));
           break;
           break;

+ 22 - 1
data/web/lang/lang.de.php

@@ -16,6 +16,9 @@ $lang['footer']['delete_these_items'] = 'Sind Sie sicher, dass die Änderungen a
 $lang['footer']['delete_now'] = 'Jetzt löschen';
 $lang['footer']['delete_now'] = 'Jetzt löschen';
 $lang['footer']['cancel'] = 'Abbrechen';
 $lang['footer']['cancel'] = 'Abbrechen';
 
 
+$lang['footer']['hibp_nok'] = 'Übereinstimmung gefunden! Dieses Passwort ist potentiell gefährlich!';
+$lang['footer']['hibp_ok'] = 'Keine Übereinstimmung gefunden.';
+
 $lang['danger']['mysql_error'] = "MySQL Fehler: %s";
 $lang['danger']['mysql_error'] = "MySQL Fehler: %s";
 $lang['danger']['redis_error'] = "Redis Fehler: %s";
 $lang['danger']['redis_error'] = "Redis Fehler: %s";
 $lang['danger']['unknown_tfa_method'] = "Unbekannte TFA Methode";
 $lang['danger']['unknown_tfa_method'] = "Unbekannte TFA Methode";
@@ -43,6 +46,7 @@ $lang['danger']['domain_cannot_match_hostname'] = "Domain darf nicht dem Hostnam
 $lang['warning']['domain_added_sogo_failed'] = "Domain wurde hinzugefügt; SOGo konnte nicht neugestartet werden";
 $lang['warning']['domain_added_sogo_failed'] = "Domain wurde hinzugefügt; SOGo konnte nicht neugestartet werden";
 $lang['danger']['rl_timeframe'] = "Ratelimit Zeitraum ist inkorrekt";
 $lang['danger']['rl_timeframe'] = "Ratelimit Zeitraum ist inkorrekt";
 $lang['success']['rl_saved'] = "Ratelimit für Objekt %s wurde gesetzt";
 $lang['success']['rl_saved'] = "Ratelimit für Objekt %s wurde gesetzt";
+$lang['success']['acl_saved'] = "ACL für Objekt %s wurde gesetzt";
 $lang['success']['deleted_syncjobs'] = "Syncjobs gelöscht: %s";
 $lang['success']['deleted_syncjobs'] = "Syncjobs gelöscht: %s";
 $lang['success']['deleted_syncjob'] = "Syncjobs ID %s gelöscht";
 $lang['success']['deleted_syncjob'] = "Syncjobs ID %s gelöscht";
 $lang['success']['delete_filters'] = "Filter gelöscht: %s";
 $lang['success']['delete_filters'] = "Filter gelöscht: %s";
@@ -332,6 +336,8 @@ $lang['edit']['relay_all_info'] = '<small>Wenn Sie <b>nicht</b> alle Empfänger-
 $lang['edit']['full_name'] = 'Voller Name';
 $lang['edit']['full_name'] = 'Voller Name';
 $lang['edit']['quota_mb'] = 'Speicherplatz (MiB)';
 $lang['edit']['quota_mb'] = 'Speicherplatz (MiB)';
 $lang['edit']['sender_acl'] = 'Darf Nachrichten versenden als';
 $lang['edit']['sender_acl'] = 'Darf Nachrichten versenden als';
+$lang['edit']['sender_acl_disabled'] = '↳ <span class="label label-danger">Absenderprüfung deaktiviert</span>';
+$lang['user']['sender_acl_disabled'] = '<span class="label label-danger">Absenderprüfung deaktiviert</span>';
 $lang['edit']['previous'] = 'Vorherige Seite';
 $lang['edit']['previous'] = 'Vorherige Seite';
 $lang['edit']['unchanged_if_empty'] = 'Unverändert, wenn leer';
 $lang['edit']['unchanged_if_empty'] = 'Unverändert, wenn leer';
 $lang['edit']['dont_check_sender_acl'] = 'Absender für Domain %s u. Alias-Dom. nicht prüfen';
 $lang['edit']['dont_check_sender_acl'] = 'Absender für Domain %s u. Alias-Dom. nicht prüfen';
@@ -339,7 +345,22 @@ $lang['edit']['multiple_bookings'] = 'Mehrfaches Buchen';
 $lang['edit']['kind'] = 'Art';
 $lang['edit']['kind'] = 'Art';
 $lang['edit']['resource'] = 'Ressource';
 $lang['edit']['resource'] = 'Ressource';
 
 
-$lang['add']['syncjob'] = 'Sync-Job erstellen';
+$lang['acl']['spam_alias'] = 'Temporäre E-Mail Aliasse';
+$lang['acl']['tls_policy'] = 'Verschlüsselungsrichtlinie';
+$lang['acl']['spam_score'] = 'Spam Bewertung';
+$lang['acl']['spam_policy'] = 'Blacklist/Whitelist';
+$lang['acl']['delimiter_action'] = 'Delimiter Aktionen (tags)';
+$lang['acl']['syncjobs'] = 'Sync Jobs';
+$lang['acl']['eas_reset'] = 'EAS-Cache zurücksetzen';
+$lang['acl']['quarantine'] = 'Quarantäne';
+$lang['acl']['login_as'] = 'Einloggen als Mailbox-Benutzer';
+$lang['acl']['bcc_maps'] = 'BCC Maps';
+$lang['acl']['filters'] = 'Filter';
+$lang['acl']['ratelimit'] = 'Rate limit';
+$lang['acl']['recipient_maps'] = 'Empfängerumschreibungen';
+$lang['acl']['prohibited'] = 'Untersagt durch Richtlinie';
+
+$lang['add']['generate'] = 'generieren';
 $lang['add']['syncjob_hint'] = 'Passwörter werden unverschlüsselt abgelegt!';
 $lang['add']['syncjob_hint'] = 'Passwörter werden unverschlüsselt abgelegt!';
 $lang['add']['hostname'] = 'Servername';
 $lang['add']['hostname'] = 'Servername';
 $lang['add']['port'] = 'Port';
 $lang['add']['port'] = 'Port';

+ 21 - 0
data/web/lang/lang.en.php

@@ -16,6 +16,9 @@ $lang['footer']['delete_these_items'] = 'Please confirm your changes to the foll
 $lang['footer']['delete_now'] = 'Delete now';
 $lang['footer']['delete_now'] = 'Delete now';
 $lang['footer']['cancel'] = 'Cancel';
 $lang['footer']['cancel'] = 'Cancel';
 
 
+$lang['footer']['hibp_nok'] = 'Matched! This is a potentially dangerous password!';
+$lang['footer']['hibp_ok'] = 'No match found.';
+
 $lang['danger']['mysql_error'] = "MySQL error: %s";
 $lang['danger']['mysql_error'] = "MySQL error: %s";
 $lang['danger']['redis_error'] = "Redis error: %s";
 $lang['danger']['redis_error'] = "Redis error: %s";
 $lang['danger']['unknown_tfa_method'] = "Unknown TFA method";
 $lang['danger']['unknown_tfa_method'] = "Unknown TFA method";
@@ -43,6 +46,7 @@ $lang['danger']['domain_cannot_match_hostname'] = "Domain cannot match hostname"
 $lang['warning']['domain_added_sogo_failed'] = "Added domain but failed to restart SOGo, please check your server logs.";
 $lang['warning']['domain_added_sogo_failed'] = "Added domain but failed to restart SOGo, please check your server logs.";
 $lang['danger']['rl_timeframe'] = "Rate limit time frame is incorrect";
 $lang['danger']['rl_timeframe'] = "Rate limit time frame is incorrect";
 $lang['success']['rl_saved'] = "Rate limit for object %s saved";
 $lang['success']['rl_saved'] = "Rate limit for object %s saved";
+$lang['success']['acl_saved'] = "ACL for object %s saved";
 $lang['success']['deleted_syncjobs'] = "Deleted syncjobs: %s";
 $lang['success']['deleted_syncjobs'] = "Deleted syncjobs: %s";
 $lang['success']['deleted_syncjob'] = "Deleted syncjob ID %s";
 $lang['success']['deleted_syncjob'] = "Deleted syncjob ID %s";
 $lang['success']['delete_filters'] = "Deleted filters: %s";
 $lang['success']['delete_filters'] = "Deleted filters: %s";
@@ -342,6 +346,7 @@ $lang['edit']['full_name'] = 'Full name';
 $lang['edit']['quota_mb'] = 'Quota (MiB)';
 $lang['edit']['quota_mb'] = 'Quota (MiB)';
 $lang['edit']['sender_acl'] = 'Allow to send as';
 $lang['edit']['sender_acl'] = 'Allow to send as';
 $lang['edit']['sender_acl_disabled'] = '↳ <span class="label label-danger">Sender check is disabled</span>';
 $lang['edit']['sender_acl_disabled'] = '↳ <span class="label label-danger">Sender check is disabled</span>';
+$lang['user']['sender_acl_disabled'] = '<span class="label label-danger">Sender check is disabled</span>';
 $lang['edit']['previous'] = 'Previous page';
 $lang['edit']['previous'] = 'Previous page';
 $lang['edit']['unchanged_if_empty'] = 'If unchanged leave blank';
 $lang['edit']['unchanged_if_empty'] = 'If unchanged leave blank';
 $lang['edit']['dont_check_sender_acl'] = "Disable sender check for domain %s (+ alias domains)";
 $lang['edit']['dont_check_sender_acl'] = "Disable sender check for domain %s (+ alias domains)";
@@ -349,6 +354,22 @@ $lang['edit']['multiple_bookings'] = 'Multiple bookings';
 $lang['edit']['kind'] = 'Kind';
 $lang['edit']['kind'] = 'Kind';
 $lang['edit']['resource'] = 'Resource';
 $lang['edit']['resource'] = 'Resource';
 
 
+$lang['acl']['spam_alias'] = 'Temporary aliases';
+$lang['acl']['tls_policy'] = 'TLS policy';
+$lang['acl']['spam_score'] = 'Spam score';
+$lang['acl']['spam_policy'] = 'Blacklist/Whitelist';
+$lang['acl']['delimiter_action'] = 'Delimiter action';
+$lang['acl']['syncjobs'] = 'Sync jobs';
+$lang['acl']['eas_reset'] = 'Reset EAS devices';
+$lang['acl']['quarantine'] = 'Quarantine';
+$lang['acl']['login_as'] = 'Login as mailbox user';
+$lang['acl']['bcc_maps'] = 'BCC maps';
+$lang['acl']['filters'] = 'Filters';
+$lang['acl']['ratelimit'] = 'Rate limit';
+$lang['acl']['recipient_maps'] = 'Recipient maps';
+$lang['acl']['prohibited'] = 'Prohibited by ACL';
+
+$lang['add']['generate'] = 'generate';
 $lang['add']['syncjob'] = 'Add sync job';
 $lang['add']['syncjob'] = 'Add sync job';
 $lang['add']['syncjob_hint'] = 'Be aware that passwords need to be saved plain-text!';
 $lang['add']['syncjob_hint'] = 'Be aware that passwords need to be saved plain-text!';
 $lang['add']['hostname'] = 'Hostname';
 $lang['add']['hostname'] = 'Hostname';

+ 6 - 9
data/web/mailbox.php

@@ -214,7 +214,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
               <table class="table table-striped" id="filter_table"></table>
               <table class="table table-striped" id="filter_table"></table>
             </div>
             </div>
             <div class="mass-actions-mailbox">
             <div class="mass-actions-mailbox">
-              <div class="btn-group">
+              <div class="btn-group" data-acl="<?=$_SESSION['acl']['filters'];?>">
                 <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="filter_item" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
                 <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="filter_item" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
                 <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
                 <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
                 <ul class="dropdown-menu">
                 <ul class="dropdown-menu">
@@ -245,7 +245,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
               <table class="table table-striped" id="bcc_table"></table>
               <table class="table table-striped" id="bcc_table"></table>
             </div>
             </div>
             <div class="mass-actions-mailbox">
             <div class="mass-actions-mailbox">
-              <div class="btn-group">
+              <div class="btn-group" data-acl="<?=$_SESSION['acl']['bcc_maps'];?>">
                 <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="bcc" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
                 <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="bcc" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
                 <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
                 <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
                 <ul class="dropdown-menu">
                 <ul class="dropdown-menu">
@@ -261,7 +261,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
               </div>
               </div>
             </div>
             </div>
           </div>
           </div>
-          <div class="panel panel-default">
+          <div class="panel panel-default <?=($_SESSION['mailcow_cc_role'] == "admin") ?: 'hidden';?>">
             <div class="panel-heading">
             <div class="panel-heading">
               <?=$lang['mailbox']['recipient_maps'];?> <span class="badge badge-info table-lines"></span>
               <?=$lang['mailbox']['recipient_maps'];?> <span class="badge badge-info table-lines"></span>
               <div class="btn-group pull-right">
               <div class="btn-group pull-right">
@@ -272,12 +272,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
             <div class="table-responsive">
             <div class="table-responsive">
               <table class="table table-striped" id="recipient_map_table"></table>
               <table class="table table-striped" id="recipient_map_table"></table>
             </div>
             </div>
-<?php
-if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin"))
-  $display = 'block';
-else
-  $display = 'none';
-?>
             <div class="mass-actions-mailbox" style="display: <?php echo $display; ?>">
             <div class="mass-actions-mailbox" style="display: <?php echo $display; ?>">
               <div class="btn-group">
               <div class="btn-group">
                 <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="recipient_map" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
                 <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="recipient_map" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
@@ -304,9 +298,12 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/mailbox.php';
 <?php
 <?php
 $lang_mailbox = json_encode($lang['mailbox']);
 $lang_mailbox = json_encode($lang['mailbox']);
 echo "var lang = ". $lang_mailbox . ";\n";
 echo "var lang = ". $lang_mailbox . ";\n";
+echo "var acl = '". json_encode($_SESSION['acl']) . "';\n";
 echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
 echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
 $role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
 $role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
+$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
 echo "var role = '". $role . "';\n";
 echo "var role = '". $role . "';\n";
+echo "var is_dual = " . $is_dual . ";\n";
 echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
 echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
 ?>
 ?>
 </script>
 </script>

+ 2 - 2
data/web/modals/admin.php

@@ -67,7 +67,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
             <div class="form-group">
             <div class="form-group">
               <label class="control-label col-sm-2" for="name"><?=$lang['admin']['admin_domains'];?>:</label>
               <label class="control-label col-sm-2" for="name"><?=$lang['admin']['admin_domains'];?>:</label>
               <div class="col-sm-10">
               <div class="col-sm-10">
-                <select title="<?=$lang['admin']['search_domain_da'];?>" style="width:100%" name="domains" size="5" multiple>
+                <select title="<?=$lang['admin']['search_domain_da'];?>" class="full-width-select" name="domains" size="5" multiple>
                 <?php
                 <?php
                 foreach (mailbox('get', 'domains') as $domain) {
                 foreach (mailbox('get', 'domains') as $domain) {
                   echo "<option>".htmlspecialchars($domain)."</option>";
                   echo "<option>".htmlspecialchars($domain)."</option>";
@@ -79,7 +79,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
             <div class="form-group">
             <div class="form-group">
               <label class="control-label col-sm-2" for="password"><?=$lang['admin']['password'];?>:</label>
               <label class="control-label col-sm-2" for="password"><?=$lang['admin']['password'];?>:</label>
               <div class="col-sm-10">
               <div class="col-sm-10">
-              <input type="password" class="form-control" name="password" id="password" placeholder="" required>
+              <input type="password" class="form-control" data-hibp="true" name="password" id="password" placeholder="" required>
               </div>
               </div>
             </div>
             </div>
             <div class="form-group">
             <div class="form-group">

+ 3 - 4
data/web/modals/mailbox.php

@@ -23,7 +23,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
           <div class="form-group">
           <div class="form-group">
             <label class="control-label col-sm-2" for="domain"><?=$lang['add']['domain'];?>:</label>
             <label class="control-label col-sm-2" for="domain"><?=$lang['add']['domain'];?>:</label>
             <div class="col-sm-10">
             <div class="col-sm-10">
-              <select data-live-search="true" id="addSelectDomain" name="domain" id="domain" required>
+              <select class="full-width-select" data-live-search="true" id="addSelectDomain" name="domain" id="domain" required>
               <?php
               <?php
               foreach (mailbox('get', 'domains') as $domain) {
               foreach (mailbox('get', 'domains') as $domain) {
                 echo "<option>".htmlspecialchars($domain)."</option>";
                 echo "<option>".htmlspecialchars($domain)."</option>";
@@ -48,10 +48,9 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
             </div>
             </div>
           </div>
           </div>
           <div class="form-group">
           <div class="form-group">
-            <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label>
+            <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?> (<a href="#" class="generate_password"><?=$lang['add']['generate'];?></a>)</label>
             <div class="col-sm-10">
             <div class="col-sm-10">
-            <input type="password" class="form-control" name="password" id="password" placeholder="" required>
-            (<a href="#" class="generate_password">Generate</a>)
+            <input type="password" data-hibp="true" class="form-control" name="password" id="password" placeholder="" required>
             </div>
             </div>
           </div>
           </div>
           <div class="form-group">
           <div class="form-group">

+ 2 - 2
data/web/modals/user.php

@@ -37,7 +37,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
 					<div class="form-group">
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="password1"><?=$lang['add']['password'];?></label>
 						<label class="control-label col-sm-2" for="password1"><?=$lang['add']['password'];?></label>
 						<div class="col-sm-10">
 						<div class="col-sm-10">
-						<input type="password" class="form-control" name="password1" id="password1" required>
+						<input type="password" class="form-control" name="password1" id="password1" data-hibp="true" required>
 						</div>
 						</div>
 					</div>
 					</div>
 					<div class="form-group">
 					<div class="form-group">
@@ -155,7 +155,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
           <div class="form-group">
           <div class="form-group">
             <label class="control-label col-sm-3" for="user_new_pass"><?=$lang['user']['new_password'];?></label>
             <label class="control-label col-sm-3" for="user_new_pass"><?=$lang['user']['new_password'];?></label>
             <div class="col-sm-5">
             <div class="col-sm-5">
-            <input type="password" class="form-control" name="user_new_pass" id="user_new_pass" autocomplete="off" required>
+            <input type="password" data-hibp="true" class="form-control" name="user_new_pass" id="user_new_pass" autocomplete="off" required>
             </div>
             </div>
           </div>
           </div>
           <div class="form-group">
           <div class="form-group">

+ 1 - 1
data/web/quarantine.php

@@ -18,7 +18,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
           <table id="quarantinetable" class="table table-striped"></table>
           <table id="quarantinetable" class="table table-striped"></table>
         </div>
         </div>
         <div class="mass-actions-quarantine">
         <div class="mass-actions-quarantine">
-          <div class="btn-group">
+          <div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine'];?>">
             <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="qitems" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['quarantine']['toggle_all'];?></a>
             <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="qitems" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['quarantine']['toggle_all'];?></a>
             <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['quarantine']['quick_actions'];?> <span class="caret"></span></a>
             <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">
             <ul class="dropdown-menu">

+ 25 - 64
data/web/user.php

@@ -88,7 +88,6 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
       fclose($fh);
       fclose($fh);
     }
     }
   }
   }
-
 ?>
 ?>
 <div class="container">
 <div class="container">
 <h3><?=$lang['user']['user_settings'];?></h3>
 <h3><?=$lang['user']['user_settings'];?></h3>
@@ -145,7 +144,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
   <div class="row">
   <div class="row">
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div>
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div>
     <div class="col-md-9 col-xs-7">
     <div class="col-md-9 col-xs-7">
-    <p><?=$user_get_alias_details['aliases_also_send_as'];?></p>
+    <p><?=($user_get_alias_details['aliases_also_send_as'] == '*') ? $lang['user']['sender_acl_disabled'] : $user_get_alias_details['aliases_also_send_as'];?></p>
     </div>
     </div>
   </div>
   </div>
   <div class="row">
   <div class="row">
@@ -172,87 +171,71 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
       <p><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=formatBytes($mailboxdata['quota'], 2);?>, <?=$mailboxdata['messages'];?> <?=$lang['user']['messages'];?></p>
       <p><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=formatBytes($mailboxdata['quota'], 2);?>, <?=$mailboxdata['messages'];?> <?=$lang['user']['messages'];?></p>
     </div>
     </div>
   </div>
   </div>
+  <hr>
   <?php
   <?php
-  ($_SESSION['acl']['delimiter_action'] == 0 && $_SESSION['acl']['delimiter_action'] == 0 && $_SESSION['acl']['delimiter_action'] == 0) ? null : '<hr>';
   // Show tagging options
   // Show tagging options
-  if ($_SESSION['acl']['delimiter_action'] == 1):
   $get_tagging_options = mailbox('get', 'delimiter_action', $username);
   $get_tagging_options = mailbox('get', 'delimiter_action', $username);
   ?>
   ?>
   <div class="row">
   <div class="row">
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tag_handling'];?>:</div>
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tag_handling'];?>:</div>
     <div class="col-md-9 col-xs-7">
     <div class="col-md-9 col-xs-7">
-    <div class="btn-group">
-
+    <div class="btn-group" data-acl="<?=$_SESSION['acl']['delimiter_action'];?>">
       <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subfolder") ? 'active' : null; ?>"
       <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subfolder") ? 'active' : null; ?>"
         id="edit_selected"
         id="edit_selected"
         data-item="<?= htmlentities($username); ?>"
         data-item="<?= htmlentities($username); ?>"
         data-id="delimiter_action"
         data-id="delimiter_action"
         data-api-url='edit/delimiter_action'
         data-api-url='edit/delimiter_action'
         data-api-attr='{"tagged_mail_handler":"subfolder"}'><?=$lang['user']['tag_in_subfolder'];?></button>
         data-api-attr='{"tagged_mail_handler":"subfolder"}'><?=$lang['user']['tag_in_subfolder'];?></button>
-
       <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subject") ? 'active' : null; ?>"
       <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subject") ? 'active' : null; ?>"
         id="edit_selected"
         id="edit_selected"
         data-item="<?= htmlentities($username); ?>"
         data-item="<?= htmlentities($username); ?>"
         data-id="delimiter_action"
         data-id="delimiter_action"
         data-api-url='edit/delimiter_action'
         data-api-url='edit/delimiter_action'
         data-api-attr='{"tagged_mail_handler":"subject"}'><?=$lang['user']['tag_in_subject'];?></button>
         data-api-attr='{"tagged_mail_handler":"subject"}'><?=$lang['user']['tag_in_subject'];?></button>
-
       <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "none") ? 'active' : null; ?>"
       <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "none") ? 'active' : null; ?>"
         id="edit_selected"
         id="edit_selected"
         data-item="<?= htmlentities($username); ?>"
         data-item="<?= htmlentities($username); ?>"
         data-id="delimiter_action"
         data-id="delimiter_action"
         data-api-url='edit/delimiter_action'
         data-api-url='edit/delimiter_action'
         data-api-attr='{"tagged_mail_handler":"none"}'><?=$lang['user']['tag_in_none'];?></button>
         data-api-attr='{"tagged_mail_handler":"none"}'><?=$lang['user']['tag_in_none'];?></button>
-
     </div>
     </div>
     <p class="help-block"><?=$lang['user']['tag_help_explain'];?></p>
     <p class="help-block"><?=$lang['user']['tag_help_explain'];?></p>
     <p class="help-block"><?=$lang['user']['tag_help_example'];?></p>
     <p class="help-block"><?=$lang['user']['tag_help_example'];?></p>
     </div>
     </div>
   </div>
   </div>
   <?php
   <?php
-  endif;
   // Show TLS policy options
   // Show TLS policy options
-  if ($_SESSION['acl']['tls_policy'] == 1):
   $get_tls_policy = mailbox('get', 'tls_policy', $username);
   $get_tls_policy = mailbox('get', 'tls_policy', $username);
   ?>
   ?>
   <div class="row">
   <div class="row">
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tls_policy'];?>:</div>
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tls_policy'];?>:</div>
     <div class="col-md-9 col-xs-7">
     <div class="col-md-9 col-xs-7">
-    <div class="btn-group">
-
+    <div class="btn-group" data-acl="<?=$_SESSION['acl']['tls_policy'];?>">
       <button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_in'] == "1") ? "active" : null;?>"
       <button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_in'] == "1") ? "active" : null;?>"
         id="edit_selected"
         id="edit_selected"
         data-item="<?= htmlentities($username); ?>"
         data-item="<?= htmlentities($username); ?>"
         data-id="tls_policy"
         data-id="tls_policy"
         data-api-url='edit/tls_policy'
         data-api-url='edit/tls_policy'
         data-api-attr='{"tls_enforce_in":<?=($get_tls_policy['tls_enforce_in'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_in'];?></button>
         data-api-attr='{"tls_enforce_in":<?=($get_tls_policy['tls_enforce_in'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_in'];?></button>
-
       <button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_out'] == "1") ? "active" : null;?>"
       <button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_out'] == "1") ? "active" : null;?>"
         id="edit_selected"
         id="edit_selected"
         data-item="<?= htmlentities($username); ?>"
         data-item="<?= htmlentities($username); ?>"
         data-id="tls_policy"
         data-id="tls_policy"
         data-api-url='edit/tls_policy'
         data-api-url='edit/tls_policy'
         data-api-attr='{"tls_enforce_out":<?=($get_tls_policy['tls_enforce_out'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_out'];?></button>
         data-api-attr='{"tls_enforce_out":<?=($get_tls_policy['tls_enforce_out'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_out'];?></button>
-
     </div>
     </div>
     <p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p>
     <p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p>
     </div>
     </div>
   </div>
   </div>
-  <?php
-  endif;
-  // Rest EAS devices
-  if ($_SESSION['acl']['eas_reset'] == 1):
-  ?>
+
   <div class="row">
   <div class="row">
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['eas_reset'];?>:</div>
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['eas_reset'];?>:</div>
     <div class="col-md-9 col-xs-7">
     <div class="col-md-9 col-xs-7">
-    <button class="btn btn-xs btn-default" id="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="eas_cache" data-api-url='delete/eas_cache' href="#"><?=$lang['user']['eas_reset_now'];?></button>
+    <button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['eas_reset'];?>" id="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="eas_cache" data-api-url='delete/eas_cache' href="#"><?=$lang['user']['eas_reset_now'];?></button>
     <p class="help-block"><?=$lang['user']['eas_reset_help'];?></p>
     <p class="help-block"><?=$lang['user']['eas_reset_help'];?></p>
     </div>
     </div>
   </div>
   </div>
-  <?php
-  endif;
-  ?>
+
 </div>
 </div>
 </div>
 </div>
 
 
@@ -273,11 +256,9 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
         </div>
         </div>
       </div>
       </div>
 		</div>
 		</div>
-    <?php
-    if ($_SESSION['acl']['spam_alias'] == 1):
-    ?>
+
     <div class="mass-actions-user">
     <div class="mass-actions-user">
-      <div class="btn-group">
+      <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_alias'];?>">
         <div class="btn-group">
         <div class="btn-group">
           <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="tla" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
           <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="tla" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
           <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
           <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
@@ -299,9 +280,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
-    <?php
-    endif;
-    ?>
+
 	</div>
 	</div>
 
 
 	<div role="tabpanel" class="tab-pane" id="Spamfilter">
 	<div role="tabpanel" class="tab-pane" id="Spamfilter">
@@ -309,7 +288,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
 		<form class="form-horizontal" role="form" data-id="spam_score" method="post">
 		<form class="form-horizontal" role="form" data-id="spam_score" method="post">
 			<div class="form-group">
 			<div class="form-group">
 				<div class="col-lg-6 col-sm-12">
 				<div class="col-lg-6 col-sm-12">
-					<input name="spam_score" id="spam_score" type="text" style="width: 100%;"
+					<input data-acl="<?=$_SESSION['acl']['spam_score'];?>" name="spam_score" id="spam_score" type="text" style="width: 100%;"
 						data-provide="slider"
 						data-provide="slider"
 						data-slider-min="1"
 						data-slider-min="1"
 						data-slider-max="2000"
 						data-slider-max="2000"
@@ -330,21 +309,17 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
 					<p><?=$lang['user']['spamfilter_hint'];?></p>
 					<p><?=$lang['user']['spamfilter_hint'];?></p>
 				</div>
 				</div>
 			</div>
 			</div>
-      <?php
-      if ($_SESSION['acl']['spam_score'] == 1):
-      ?>
+
       <div class="form-group">
       <div class="form-group">
 				<div class="col-sm-10">
 				<div class="col-sm-10">
-        <button type="button" class="btn btn-sm btn-success" id="edit_selected"
+        <button data-acl="<?=$_SESSION['acl']['spam_score'];?>" type="button" class="btn btn-sm btn-success" id="edit_selected"
           data-item="<?= htmlentities($username); ?>"
           data-item="<?= htmlentities($username); ?>"
           data-id="spam_score"
           data-id="spam_score"
           data-api-url='edit/spam-score'
           data-api-url='edit/spam-score'
           data-api-attr='{}'><?=$lang['user']['save_changes'];?></button>
           data-api-attr='{}'><?=$lang['user']['save_changes'];?></button>
 				</div>
 				</div>
 			</div>
 			</div>
-      <?php
-      endif;
-      ?>
+
 		</form>
 		</form>
 		<hr>
 		<hr>
 		<div class="row">
 		<div class="row">
@@ -354,26 +329,22 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
         <div class="table-responsive">
         <div class="table-responsive">
           <table class="table table-striped table-condensed" id="wl_policy_mailbox_table"></table>
           <table class="table table-striped table-condensed" id="wl_policy_mailbox_table"></table>
         </div>
         </div>
-        <?php
-        if ($_SESSION['acl']['spam_policy'] == 1):
-        ?>
+
         <div class="mass-actions-user">
         <div class="mass-actions-user">
-          <div class="btn-group">
+          <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
             <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
             <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
             <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
             <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
           </div>
           </div>
         </div>
         </div>
         <form class="form-inline" data-id="add_wl_policy_mailbox">
         <form class="form-inline" data-id="add_wl_policy_mailbox">
-          <div class="input-group">
+          <div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
             <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
             <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
             <span class="input-group-btn">
             <span class="input-group-btn">
               <button class="btn btn-default" id="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"wl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button>
               <button class="btn btn-default" id="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"wl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button>
             </span>
             </span>
           </div>
           </div>
         </form>
         </form>
-        <?php
-        endif;
-        ?>
+
       </div>
       </div>
 			<div class="col-sm-6">
 			<div class="col-sm-6">
 				<h4><?=$lang['user']['spamfilter_bl'];?></h4>
 				<h4><?=$lang['user']['spamfilter_bl'];?></h4>
@@ -381,28 +352,22 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
         <div class="table-responsive">
         <div class="table-responsive">
           <table class="table table-striped table-condensed" id="bl_policy_mailbox_table"></table>
           <table class="table table-striped table-condensed" id="bl_policy_mailbox_table"></table>
         </div>
         </div>
-        <?php
-        if ($_SESSION['acl']['spam_policy'] == 1):
-        ?>
+
         <div class="mass-actions-user">
         <div class="mass-actions-user">
-          <div class="btn-group">
+          <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
             <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
             <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
             <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
             <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
           </div>
           </div>
         </div>
         </div>
         <form class="form-inline" data-id="add_bl_policy_mailbox">
         <form class="form-inline" data-id="add_bl_policy_mailbox">
-          <div class="input-group">
+          <div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
             <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
             <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
-            <input type="hidden" name="username" value="<?= htmlentities($username) ;?>">
-            <input type="hidden" name="object_list" value="bl">
             <span class="input-group-btn">
             <span class="input-group-btn">
               <button class="btn btn-default" id="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"bl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button>
               <button class="btn btn-default" id="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"bl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button>
             </span>
             </span>
           </div>
           </div>
         </form>
         </form>
-        <?php
-        endif;
-        ?>
+
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
@@ -411,11 +376,9 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
 		<div class="table-responsive">
 		<div class="table-responsive">
       <table class="table table-striped" id="sync_job_table"></table>
       <table class="table table-striped" id="sync_job_table"></table>
 		</div>
 		</div>
-    <?php
-    if ($_SESSION['acl']['syncjobs'] == 1):
-    ?>
+
     <div class="mass-actions-user">
     <div class="mass-actions-user">
-      <div class="btn-group">
+      <div class="btn-group" data-acl="<?=$_SESSION['acl']['syncjobs'];?>">
         <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
         <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
         <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
         <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
         <ul class="dropdown-menu">
         <ul class="dropdown-menu">
@@ -427,9 +390,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
         <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a>
         <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a>
       </div>
       </div>
     </div>
     </div>
-    <?php
-    endif;
-    ?>
+
 		</div>
 		</div>
 	</div>
 	</div>
 
 

+ 1 - 1
docker-compose.yml

@@ -330,7 +330,7 @@ services:
         - /lib/modules:/lib/modules:ro
         - /lib/modules:/lib/modules:ro
 
 
     watchdog-mailcow:
     watchdog-mailcow:
-      image: mailcow/watchdog:1.19
+      image: mailcow/watchdog:1.21
       # Debug
       # Debug
       #command: /watchdog.sh
       #command: /watchdog.sh
       build: ./data/Dockerfiles/watchdog
       build: ./data/Dockerfiles/watchdog