Browse Source

[Rspamd] Fix map permissions for www
[Web] Allow to manage global maps in UI (WIP)

andryyy 5 years ago
parent
commit
b52ee0a706

+ 6 - 2
data/Dockerfiles/rspamd/docker-entrypoint.sh

@@ -8,6 +8,8 @@ touch /etc/rspamd/rspamd.conf.local \
 
 
 chmod 755 /var/lib/rspamd
 chmod 755 /var/lib/rspamd
 
 
+addgroup --system --gid 82 www-access
+
 [[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Autogenerated by mailcow' > /etc/rspamd/override.d/worker-controller-password.inc
 [[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Autogenerated by mailcow' > /etc/rspamd/override.d/worker-controller-password.inc
 
 
 DOVECOT_V4=
 DOVECOT_V4=
@@ -49,8 +51,10 @@ touch /etc/rspamd/custom/global_mime_from_blacklist.map \
   /etc/rspamd/custom/bad_words_de.map
   /etc/rspamd/custom/bad_words_de.map
 
 
 # www-data (82) group needs to write to these files
 # www-data (82) group needs to write to these files
-chown -R _rspamd:82 /etc/rspamd/custom
-chmod -R g+w /etc/rspamd/custom
+chown root:root /etc/rspamd/custom/
+chmod 0755 /etc/rspamd/custom/
+chown -R _rspamd:www-access /etc/rspamd/custom/*
+chmod -R 664 /etc/rspamd/custom/*
 
 
 # Run hooks
 # Run hooks
 for file in /hooks/*; do
 for file in /hooks/*; do

+ 44 - 0
data/web/admin.php

@@ -17,6 +17,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
     <li role="presentation"><a href="#tab-routing" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['routing'];?></a></li>
     <li role="presentation"><a href="#tab-routing" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['routing'];?></a></li>
     <li role="presentation"><a href="#tab-sys-mails" aria-controls="tab-sys-mails" role="tab" data-toggle="tab"><?=$lang['admin']['sys_mails'];?></a></li>
     <li role="presentation"><a href="#tab-sys-mails" aria-controls="tab-sys-mails" role="tab" data-toggle="tab"><?=$lang['admin']['sys_mails'];?></a></li>
     <li role="presentation"><a href="#tab-mailq" aria-controls="tab-mailq" role="tab" data-toggle="tab"><?=$lang['admin']['queue_manager'];?></a></li>
     <li role="presentation"><a href="#tab-mailq" aria-controls="tab-mailq" role="tab" data-toggle="tab"><?=$lang['admin']['queue_manager'];?></a></li>
+    <li role="presentation"><a href="#tab-rspamdmaps" aria-controls="tab-rspamdmaps" role="tab" data-toggle="tab"><?=$lang['admin']['rspamd_global_filters'];?></a></li>
   </ul>
   </ul>
 
 
   <div class="row">
   <div class="row">
@@ -1135,6 +1136,49 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
+  
+  <div role="tabpanel" class="tab-pane" id="tab-rspamdmaps">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <?=$lang['admin']['rspamd_global_filters'];?>
+      </div>
+      <div class="panel-body">
+        <p><?=$lang['admin']['rspamd_global_filters_info'];?></p>
+        <div id="confirm_show_rspamd_global_filters" class="<?=($_SESSION['show_rspamd_global_filters'] === true) ? 'hidden' : '';?>">
+          <div class="form-group">
+            <div class="col-sm-offset-2 col-sm-10">
+              <label>
+                <input type="checkbox" id="show_rspamd_global_filters"> <?=$lang['admin']['rspamd_global_filters_agree'];?>
+              </label>
+            </div>
+          </div>
+        </div>
+        <div id="rspamd_global_filters" class="<?=($_SESSION['show_rspamd_global_filters'] !== true) ? 'hidden' : '';?>">
+        <?php
+        foreach ($RSPAMD_MAPS as $rspamd_map):
+        ?>
+        <hr>
+        <form class="form-horizontal" data-id="<?=$rspamd_map;?>" role="form" method="post">
+          <div class="form-group">
+            <label class="control-label col-sm-3" for="<?=$rspamd_map;?>"><code><?=$rspamd_map;?></code>:</label>
+            <div class="col-sm-9">
+              <textarea id="<?=$rspamd_map;?>" spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="10" name="rspamd_map_data" required><?=file_get_contents('/rspamd_custom_maps/' . $rspamd_map);?></textarea>
+            </div>
+          </div>
+          <div class="form-group">
+            <div class="col-sm-offset-3 col-sm-9">
+              <button class="btn btn-xs btn-default validate_rspamd_regex" data-regex-map="<?=$rspamd_map;?>" href="#"><?=$lang['add']['validate'];?></button>
+              <button class="btn btn-xs btn-success submit_rspamd_regex" data-action="edit_selected" data-id="<?=$rspamd_map;?>" data-item="<?=htmlspecialchars($rspamd_map);?>" data-api-url='edit/rspamd-map' data-api-attr='{}' href="#" disabled><?=$lang['edit']['save'];?></button>
+            </div>
+          </div>
+        </form>
+        <?php
+        endforeach;
+        ?>
+        </div>
+      </div>
+    </div>
+  </div>
 
 
   </div> <!-- /tab-content -->
   </div> <!-- /tab-content -->
   </div> <!-- /col-md-12 -->
   </div> <!-- /col-md-12 -->

+ 25 - 0
data/web/inc/ajax/regex_validation.php

@@ -0,0 +1,25 @@
+<?php
+session_start();
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+header('Content-Type: application/json');
+if (!isset($_SESSION['mailcow_cc_role'])) {
+  exit();
+}
+if (isset($_GET['regex'])) {
+  $regex_lines = preg_split("/(\r\n|\n|\r)/", $_GET['regex']);
+  foreach ($regex_lines as $line => $regex) {
+    if (empty($regex) || substr($regex, 0, 1) == "#") {
+      continue;
+    }
+    if (empty($regex) || substr($regex, 0, 1) != "/") {
+      echo json_encode(array('type' => 'danger', 'msg' => 'Line ' . ($line + 1) . ': Invalid regex'));
+      exit();
+    }    
+    if (@preg_match($regex, 'Lorem Ipsum') === false) {
+      echo json_encode(array('type' => 'danger', 'msg' => 'Line ' . ($line + 1) . ': Invalid regex "' . $regex . '"'));
+      exit();
+    }
+  }
+  echo json_encode(array('type' => 'success', 'msg' => $lang['add']['validation_success']));
+}
+?>

+ 3 - 0
data/web/inc/ajax/show_rspamd_global_filters.php

@@ -0,0 +1,3 @@
+<?php
+session_start();
+$_SESSION['show_rspamd_global_filters'] = true;

+ 2 - 2
data/web/inc/functions.fail2ban.inc.php

@@ -32,7 +32,7 @@ function fail2ban($_action, $_data = null) {
             $tmp_wl_data[] = $key;
             $tmp_wl_data[] = $key;
           }
           }
           if (isset($tmp_wl_data)) {
           if (isset($tmp_wl_data)) {
-            sort($tmp_wl_data);
+            natsort($tmp_wl_data);
             $f2b_options['whitelist'] = implode(PHP_EOL, $tmp_wl_data);
             $f2b_options['whitelist'] = implode(PHP_EOL, $tmp_wl_data);
           }
           }
           else {
           else {
@@ -48,7 +48,7 @@ function fail2ban($_action, $_data = null) {
             $tmp_bl_data[] = $key;
             $tmp_bl_data[] = $key;
           }
           }
           if (isset($tmp_bl_data)) {
           if (isset($tmp_bl_data)) {
-            sort($tmp_bl_data);
+            natsort($tmp_bl_data);
             $f2b_options['blacklist'] = implode(PHP_EOL, $tmp_bl_data);
             $f2b_options['blacklist'] = implode(PHP_EOL, $tmp_bl_data);
           }
           }
           else {
           else {

+ 0 - 160
data/web/inc/functions.rsettings.inc.php

@@ -1,160 +0,0 @@
-<?php
-function rsettings($_action, $_data = null) {
-	global $pdo;
-	global $lang;
-  $_data_log = $_data;
-  switch ($_action) {
-    case 'add':
-      if ($_SESSION['mailcow_cc_role'] != "admin") {
-        $_SESSION['return'][] = array(
-          'type' => 'danger',
-          'log' => array(__FUNCTION__, $_action, $_data_log),
-          'msg' => 'access_denied'
-        );
-        return false;
-      }
-      $content = $_data['content'];
-      $desc = $_data['desc'];
-      $active = intval($_data['active']);
-      if (empty($content)) {
-        $_SESSION['return'][] = array(
-          'type' => 'danger',
-          'log' => array(__FUNCTION__, $_action, $_data_log),
-          'msg' => 'map_content_empty'
-        );
-        return false;
-      }
-      try {
-        $stmt = $pdo->prepare("INSERT INTO `settingsmap` (`content`, `desc`, `active`)
-          VALUES (:content, :desc, :active)");
-        $stmt->execute(array(
-          ':content' => $content,
-          ':desc' => $desc,
-          ':active' => $active
-        ));
-      }
-      catch (PDOException $e) {
-        $_SESSION['return'][] = array(
-          'type' => 'danger',
-          'log' => array(__FUNCTION__, $_action, $_data_log),
-          'msg' => array('mysql_error', $e)
-        );
-        return false;
-      }
-      $_SESSION['return'][] = array(
-        'type' => 'success',
-        'log' => array(__FUNCTION__, $_action, $_data_log),
-        'msg' => 'settings_map_added'
-      );
-    break;
-    case 'edit':
-      if ($_SESSION['mailcow_cc_role'] != "admin") {
-        $_SESSION['return'][] = array(
-          'type' => 'danger',
-          'log' => array(__FUNCTION__, $_action, $_data_log),
-          'msg' => 'access_denied'
-        );
-        return false;
-      }
-      $ids = (array)$_data['id'];
-      foreach ($ids as $id) {
-        $is_now = rsettings('details', $id);
-        if (!empty($is_now)) {
-          $content = (!empty($_data['content'])) ? $_data['content'] : $is_now['content'];
-          $desc = (!empty($_data['desc'])) ? $_data['desc'] : $is_now['desc'];
-          $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
-        }
-        else {
-          $_SESSION['return'][] = array(
-            'type' => 'danger',
-            'log' => array(__FUNCTION__, $_action, $_data_log),
-            'msg' => array('settings_map_invalid', $id)
-          );
-          continue;
-        }
-        $content = trim($content);
-        try {
-          $stmt = $pdo->prepare("UPDATE `settingsmap` SET
-            `content` = :content,
-            `desc` = :desc,
-            `active` = :active
-              WHERE `id` = :id");
-          $stmt->execute(array(
-            ':content' => $content,
-            ':desc' => $desc,
-            ':active' => $active,
-            ':id' => $id
-          ));
-        }
-        catch (PDOException $e) {
-          $_SESSION['return'][] = array(
-            'type' => 'danger',
-            'log' => array(__FUNCTION__, $_action, $_data_log),
-            'msg' => array('mysql_error', $e)
-          );
-          continue;
-        }
-        $_SESSION['return'][] = array(
-          'type' => 'success',
-          'log' => array(__FUNCTION__, $_action, $_data_log),
-          'msg' => array('object_modified', htmlspecialchars($ids))
-        );
-      }
-    break;
-    case 'delete':
-      if ($_SESSION['mailcow_cc_role'] != "admin") {
-        $_SESSION['return'][] = array(
-          'type' => 'danger',
-          'log' => array(__FUNCTION__, $_action, $_data_log),
-          'msg' => 'access_denied'
-        );
-        return false;
-      }
-      $ids = (array)$_data['id'];
-      foreach ($ids as $id) {
-        try {
-          $stmt = $pdo->prepare("DELETE FROM `settingsmap` WHERE `id`= :id");
-          $stmt->execute(array(':id' => $id));
-        }
-        catch (PDOException $e) {
-          $_SESSION['return'][] = array(
-            'type' => 'danger',
-            'log' => array(__FUNCTION__, $_action, $_data_log),
-            'msg' => array('mysql_error', $e)
-          );
-          return false;
-        }
-        $_SESSION['return'][] = array(
-          'type' => 'success',
-          'log' => array(__FUNCTION__, $_action, $_data_log),
-          'msg' => array('settings_map_removed', htmlspecialchars($id))
-        );
-      }
-    break;
-    case 'get':
-      if ($_SESSION['mailcow_cc_role'] != "admin") {
-        return false;
-      }
-      $settingsmaps = array();
-      $stmt = $pdo->query("SELECT `id`, `desc`, `active` FROM `settingsmap`");
-      $settingsmaps = $stmt->fetchAll(PDO::FETCH_ASSOC);
-      return $settingsmaps;
-    break;
-    case 'details':
-      if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
-        return false;
-      }
-      $settingsmapdata = array();
-      $stmt = $pdo->prepare("SELECT `id`,
-        `desc`,
-        `content`,
-        `active` AS `active_int`,
-        CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
-          FROM `settingsmap`
-            WHERE `id` = :id");
-      $stmt->execute(array(':id' => $_data));
-      $settingsmapdata = $stmt->fetch(PDO::FETCH_ASSOC);
-      return $settingsmapdata;
-    break;
-  }
-}

+ 311 - 0
data/web/inc/functions.rspamd.inc.php

@@ -0,0 +1,311 @@
+<?php
+function rsettings($_action, $_data = null) {
+	global $pdo;
+	global $lang;
+  $_data_log = $_data;
+  switch ($_action) {
+    case 'add':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
+      $content = $_data['content'];
+      $desc = $_data['desc'];
+      $active = intval($_data['active']);
+      if (empty($content)) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => 'map_content_empty'
+        );
+        return false;
+      }
+      try {
+        $stmt = $pdo->prepare("INSERT INTO `settingsmap` (`content`, `desc`, `active`)
+          VALUES (:content, :desc, :active)");
+        $stmt->execute(array(
+          ':content' => $content,
+          ':desc' => $desc,
+          ':active' => $active
+        ));
+      }
+      catch (PDOException $e) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => array('mysql_error', $e)
+        );
+        return false;
+      }
+      $_SESSION['return'][] = array(
+        'type' => 'success',
+        'log' => array(__FUNCTION__, $_action, $_data_log),
+        'msg' => 'settings_map_added'
+      );
+    break;
+    case 'edit':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
+      $ids = (array)$_data['id'];
+      foreach ($ids as $id) {
+        $is_now = rsettings('details', $id);
+        if (!empty($is_now)) {
+          $content = (!empty($_data['content'])) ? $_data['content'] : $is_now['content'];
+          $desc = (!empty($_data['desc'])) ? $_data['desc'] : $is_now['desc'];
+          $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
+        }
+        else {
+          $_SESSION['return'][] = array(
+            'type' => 'danger',
+            'log' => array(__FUNCTION__, $_action, $_data_log),
+            'msg' => array('settings_map_invalid', $id)
+          );
+          continue;
+        }
+        $content = trim($content);
+        try {
+          $stmt = $pdo->prepare("UPDATE `settingsmap` SET
+            `content` = :content,
+            `desc` = :desc,
+            `active` = :active
+              WHERE `id` = :id");
+          $stmt->execute(array(
+            ':content' => $content,
+            ':desc' => $desc,
+            ':active' => $active,
+            ':id' => $id
+          ));
+        }
+        catch (PDOException $e) {
+          $_SESSION['return'][] = array(
+            'type' => 'danger',
+            'log' => array(__FUNCTION__, $_action, $_data_log),
+            'msg' => array('mysql_error', $e)
+          );
+          continue;
+        }
+        $_SESSION['return'][] = array(
+          'type' => 'success',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => array('object_modified', htmlspecialchars($ids))
+        );
+      }
+    break;
+    case 'delete':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
+      $ids = (array)$_data['id'];
+      foreach ($ids as $id) {
+        try {
+          $stmt = $pdo->prepare("DELETE FROM `settingsmap` WHERE `id`= :id");
+          $stmt->execute(array(':id' => $id));
+        }
+        catch (PDOException $e) {
+          $_SESSION['return'][] = array(
+            'type' => 'danger',
+            'log' => array(__FUNCTION__, $_action, $_data_log),
+            'msg' => array('mysql_error', $e)
+          );
+          return false;
+        }
+        $_SESSION['return'][] = array(
+          'type' => 'success',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => array('settings_map_removed', htmlspecialchars($id))
+        );
+      }
+    break;
+    case 'get':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        return false;
+      }
+      $settingsmaps = array();
+      $stmt = $pdo->query("SELECT `id`, `desc`, `active` FROM `settingsmap`");
+      $settingsmaps = $stmt->fetchAll(PDO::FETCH_ASSOC);
+      return $settingsmaps;
+    break;
+    case 'details':
+      if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
+        return false;
+      }
+      $settingsmapdata = array();
+      $stmt = $pdo->prepare("SELECT `id`,
+        `desc`,
+        `content`,
+        `active` AS `active_int`,
+        CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
+          FROM `settingsmap`
+            WHERE `id` = :id");
+      $stmt->execute(array(':id' => $_data));
+      $settingsmapdata = $stmt->fetch(PDO::FETCH_ASSOC);
+      return $settingsmapdata;
+    break;
+  }
+}
+function rspamd($_action, $_data = null) {
+	global $pdo;
+	global $lang;
+	global $RSPAMD_MAPS;
+  $_data_log = $_data;
+  switch ($_action) {
+    case 'add':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
+      $content = $_data['content'];
+      $desc = $_data['desc'];
+      $active = intval($_data['active']);
+      if (empty($content)) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => 'map_content_empty'
+        );
+        return false;
+      }
+      try {
+        $stmt = $pdo->prepare("INSERT INTO `settingsmap` (`content`, `desc`, `active`)
+          VALUES (:content, :desc, :active)");
+        $stmt->execute(array(
+          ':content' => $content,
+          ':desc' => $desc,
+          ':active' => $active
+        ));
+      }
+      catch (PDOException $e) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => array('mysql_error', $e)
+        );
+        return false;
+      }
+      $_SESSION['return'][] = array(
+        'type' => 'success',
+        'log' => array(__FUNCTION__, $_action, $_data_log),
+        'msg' => 'settings_map_added'
+      );
+    break;
+    case 'edit':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
+      $maps = (array)$_data['map'];
+      foreach ($maps as $map) {
+        if (!in_array($map, $RSPAMD_MAPS)) {
+          $_SESSION['return'][] = array(
+            'type' => 'danger',
+            'log' => array(__FUNCTION__, $_action, $_data_log),
+            'msg' => array('global_map_invalid', $map)
+          );
+          continue;
+        }
+        try {
+          if (file_exists('/rspamd_custom_maps/' . $map)) {
+            $map_content = trim($_data['rspamd_map_data']);
+            $map_handle = fopen('/rspamd_custom_maps/' . $map, 'w');
+            if (!$map_handle) {
+              throw new Exception('File cannot be opened for writing.');
+            }
+            fwrite($map_handle, $map_content . PHP_EOL);
+            fclose($map_handle);
+          }
+        }
+        catch (Exception $e) {
+          $_SESSION['return'][] = array(
+            'type' => 'danger',
+            'log' => array(__FUNCTION__, $_action, $_data_log),
+            'msg' => array('global_map_write_error', htmlspecialchars($map), htmlspecialchars($e->getMessage()))
+          );
+          continue;
+        }
+        $_SESSION['return'][] = array(
+          'type' => 'success',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => array('object_modified', htmlspecialchars($map))
+        );
+      }
+    break;
+    case 'delete':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
+      $ids = (array)$_data['id'];
+      foreach ($ids as $id) {
+        try {
+          $stmt = $pdo->prepare("DELETE FROM `settingsmap` WHERE `id`= :id");
+          $stmt->execute(array(':id' => $id));
+        }
+        catch (PDOException $e) {
+          $_SESSION['return'][] = array(
+            'type' => 'danger',
+            'log' => array(__FUNCTION__, $_action, $_data_log),
+            'msg' => array('mysql_error', $e)
+          );
+          return false;
+        }
+        $_SESSION['return'][] = array(
+          'type' => 'success',
+          'log' => array(__FUNCTION__, $_action, $_data_log),
+          'msg' => array('settings_map_removed', htmlspecialchars($id))
+        );
+      }
+    break;
+    case 'get':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        return false;
+      }
+      $settingsmaps = array();
+      $stmt = $pdo->query("SELECT `id`, `desc`, `active` FROM `settingsmap`");
+      $settingsmaps = $stmt->fetchAll(PDO::FETCH_ASSOC);
+      return $settingsmaps;
+    break;
+    case 'details':
+      if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
+        return false;
+      }
+      $settingsmapdata = array();
+      $stmt = $pdo->prepare("SELECT `id`,
+        `desc`,
+        `content`,
+        `active` AS `active_int`,
+        CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
+          FROM `settingsmap`
+            WHERE `id` = :id");
+      $stmt->execute(array(':id' => $_data));
+      $settingsmapdata = $stmt->fetch(PDO::FETCH_ASSOC);
+      return $settingsmapdata;
+    break;
+  }
+}

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

@@ -209,7 +209,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.oauth2.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.oauth2.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.transports.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.transports.inc.php';
-require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rspamd.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php';

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

@@ -156,7 +156,7 @@ $MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'] = 'hourly';
 $MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format'] = 'maildir:';
 $MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format'] = 'maildir:';
 
 
 // Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
 // Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
-$RSPAM_MAPS = array(
+$RSPAMD_MAPS = array(
   'global_mime_from_blacklist.map',
   'global_mime_from_blacklist.map',
   'global_mime_from_whitelist.map',
   'global_mime_from_whitelist.map',
   'global_rcpt_blacklist.map',
   'global_rcpt_blacklist.map',

+ 28 - 0
data/web/js/site/admin.js

@@ -3,6 +3,7 @@ var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456
 jQuery(function($){
 jQuery(function($){
   // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
   // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
   var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
   var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
+  function jq(myid) {return "#" + myid.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1" );}
   function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
   function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
   function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
   function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
   function hashCode(t){for(var n=0,r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return n}
   function hashCode(t){for(var n=0,r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return n}
@@ -29,6 +30,33 @@ jQuery(function($){
   $("#mass_exclude").change(function(){ $("#mass_include").selectpicker('deselectAll'); });
   $("#mass_exclude").change(function(){ $("#mass_include").selectpicker('deselectAll'); });
   $("#mass_include").change(function(){ $("#mass_exclude").selectpicker('deselectAll'); });
   $("#mass_include").change(function(){ $("#mass_exclude").selectpicker('deselectAll'); });
   $("#mass_disarm").click(function() { $("#mass_send").attr("disabled", !this.checked); });
   $("#mass_disarm").click(function() { $("#mass_send").attr("disabled", !this.checked); });
+  $(".validate_rspamd_regex").click(function( event ) {
+    event.preventDefault();
+    var regex_map_id = $(this).data('regex-map');
+    var regex_data = $(jq(regex_map_id)).val();
+    $.ajax({
+      dataType: 'json',
+      url: "/inc/ajax/regex_validation.php",
+      type: "get",
+      data: { regex: regex_data },
+      complete: function(data) {
+        var response = (data.responseText);
+        response_obj = JSON.parse(response);
+        if (response_obj.type == "success") {
+          $('button[data-id="' + regex_map_id + '"]').attr({"disabled": false});
+        }
+        mailcow_alert_box(response_obj.msg, response_obj.type);
+      },
+    });
+  });
+	$('.textarea-code').on('keyup', function() {
+    $('.submit_rspamd_regex').attr({"disabled": true});
+	});
+  $("#show_rspamd_global_filters").click(function() {
+    $.get("inc/ajax/show_rspamd_global_filters.php");
+    $("#confirm_show_rspamd_global_filters").hide();
+    $("#rspamd_global_filters").removeClass("hidden");
+  });
   $("#super_delete").click(function() { return confirm(lang.queue_ays); });
   $("#super_delete").click(function() { return confirm(lang.queue_ays); });
   $(".refresh_table").on('click', function(e) {
   $(".refresh_table").on('click', function(e) {
     e.preventDefault();
     e.preventDefault();

+ 2 - 3
data/web/js/site/mailbox.js

@@ -133,16 +133,15 @@ $(document).ready(function() {
     $(e.currentTarget).find('#sieveDataText').html('<pre style="font-size:14px;line-height:1.1">' + sieveScript + '</pre>');
     $(e.currentTarget).find('#sieveDataText').html('<pre style="font-size:14px;line-height:1.1">' + sieveScript + '</pre>');
   });
   });
   // Disable submit button on script change
   // Disable submit button on script change
-	$('#script_data').on('keyup', function() {
+	$('.textarea-code').on('keyup', function() {
     $('#add_filter_btns > #add_sieve_script').attr({"disabled": true});
     $('#add_filter_btns > #add_sieve_script').attr({"disabled": true});
-    $('#validation_msg').html('-');
 	});
 	});
   // Validate script data
   // Validate script data
   $("#validate_sieve").click(function( event ) {
   $("#validate_sieve").click(function( event ) {
     event.preventDefault();
     event.preventDefault();
     var script = $('#script_data').val();
     var script = $('#script_data').val();
     $.ajax({
     $.ajax({
-      dataType: 'jsonp',
+      dataType: 'json',
       url: "/inc/ajax/sieve_validation.php",
       url: "/inc/ajax/sieve_validation.php",
       type: "get",
       type: "get",
       data: { script: script },
       data: { script: script },

+ 3 - 0
data/web/json_api.php

@@ -1245,6 +1245,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "alias":
           case "alias":
             process_edit_return(mailbox('edit', 'alias', array_merge(array('id' => $items), $attr)));
             process_edit_return(mailbox('edit', 'alias', array_merge(array('id' => $items), $attr)));
           break;
           break;
+          case "rspamd-map":
+            process_edit_return(rspamd('edit', array_merge(array('map' => $items), $attr)));
+          break;
           case "app_links":
           case "app_links":
             process_edit_return(customize('edit', 'app_links', $attr));
             process_edit_return(customize('edit', 'app_links', $attr));
           break;
           break;

+ 8 - 2
data/web/lang/lang.de.php

@@ -71,6 +71,8 @@ $lang['danger']['private_key_error'] = "Schlüsselfehler: %s";
 $lang['danger']['map_content_empty'] = "Inhalt darf nicht leer sein";
 $lang['danger']['map_content_empty'] = "Inhalt darf nicht leer sein";
 $lang['success']['settings_map_added'] = "Regel wurde gespeichert";
 $lang['success']['settings_map_added'] = "Regel wurde gespeichert";
 $lang['danger']['settings_map_invalid'] = "Regel ID %s ist ungültig";
 $lang['danger']['settings_map_invalid'] = "Regel ID %s ist ungültig";
+$lang['danger']['global_map_invalid'] = "Rspamd Map %s ist ungültig";
+$lang['danger']['global_map_write_error'] = "Kann globale Map ID %s nicht schreiben: %s";
 $lang['success']['settings_map_removed'] = "Regeln wurden entfernt: %s";
 $lang['success']['settings_map_removed'] = "Regeln wurden entfernt: %s";
 $lang['danger']['invalid_host'] = "Ungültiger Host: %s";
 $lang['danger']['invalid_host'] = "Ungültiger Host: %s";
 $lang['danger']['relayhost_invalid'] = "Mapeintrag %s ist ungültig";
 $lang['danger']['relayhost_invalid'] = "Mapeintrag %s ist ungültig";
@@ -915,5 +917,9 @@ $lang['mailbox']['alias_domain_backupmx'] = 'Alias-Domain für Relay-Domain inak
 $lang['danger']['extra_acl_invalid'] = 'Externe Absenderadresse "%s" ist ungültig';
 $lang['danger']['extra_acl_invalid'] = 'Externe Absenderadresse "%s" ist ungültig';
 $lang['danger']['extra_acl_invalid_domain'] = 'Externe Absenderadresse "%s" verwendet eine ungültige Domain';
 $lang['danger']['extra_acl_invalid_domain'] = 'Externe Absenderadresse "%s" verwendet eine ungültige Domain';
 
 
-$lang['admin']['rspamd_global_filters'] = 'Globale Rspamd Filter';
-$lang['admin']['rspamd_global_filters'] = 'Globale Rspamd Filter';
+$lang['admin']['rspamd_global_filters_agree'] = "Ich werde vorsichtig sein!";
+$lang['admin']['rspamd_global_filters'] = 'Globale Filter-Maps';
+$lang['admin']['rspamd_global_filters_info'] = 'Globale Filter-Maps steuern globales White- und Blacklisting dieses Servers. Die akzeptierte Form für Einträge sind <b>ausschließlich</b> Regular Expressions.
+  Trotz rudimentärer Überprüfung der Map, kann es zu fehlerhaften Einträgen kommen, die Rspamd im schlechtesten Fall mit unvorhersehbarer Funktionalität bestraft.<br>
+  Das korrekte Format lautet "/pattern/options" (Beispiel: <code>/.+@domain\.tld/i</code>).<br>
+  Der Name der Map beschreibt die jeweilige Funktion.';

+ 7 - 2
data/web/lang/lang.en.php

@@ -72,6 +72,8 @@ $lang['danger']['private_key_error'] = "Private key error: %s";
 $lang['danger']['map_content_empty'] = "Map content cannot be empty";
 $lang['danger']['map_content_empty'] = "Map content cannot be empty";
 $lang['success']['settings_map_added'] = "Added settings map entry";
 $lang['success']['settings_map_added'] = "Added settings map entry";
 $lang['danger']['settings_map_invalid'] = "Settings map ID %s invalid";
 $lang['danger']['settings_map_invalid'] = "Settings map ID %s invalid";
+$lang['danger']['global_map_invalid'] = "Global map ID %s invalid";
+$lang['danger']['global_map_write_error'] = "Could not write global map ID %s: %s";
 $lang['success']['settings_map_removed'] = "Removed settings map ID %s";
 $lang['success']['settings_map_removed'] = "Removed settings map ID %s";
 $lang['danger']['invalid_host'] = "Invalid host specified: %s";
 $lang['danger']['invalid_host'] = "Invalid host specified: %s";
 $lang['danger']['relayhost_invalid'] = "Map entry %s is invalid";
 $lang['danger']['relayhost_invalid'] = "Map entry %s is invalid";
@@ -940,5 +942,8 @@ $lang['mailbox']['alias_domain_backupmx'] = 'Alias domain inactive for relay dom
 $lang['danger']['extra_acl_invalid'] = 'External sender address "%s" is invalid';
 $lang['danger']['extra_acl_invalid'] = 'External sender address "%s" is invalid';
 $lang['danger']['extra_acl_invalid_domain'] = 'External sender "%s" uses an invalid domain';
 $lang['danger']['extra_acl_invalid_domain'] = 'External sender "%s" uses an invalid domain';
 
 
-$lang['admin']['rspamd_global_filters'] = 'Global Rspamd filters';
-$lang['admin']['rspamd_global_filters_info'] = 'Global Rspamd filters';
+$lang['admin']['rspamd_global_filters_agree'] = "I will be careful!";
+$lang['admin']['rspamd_global_filters'] = 'Global filter maps';
+$lang['admin']['rspamd_global_filters_info'] = 'Global filter maps contain different kind of global black and whitelists. Their names explain their purpose. All content must contain valid regular expression in the format of "/pattern/options" (e.g. <code>/.+@domain\.tld/i</code>).<br>
+  Although rudimentary checks are being executed on each line of regex, Rspamds functionality can be broken, if it fails to read the syntax correctly.';
+