浏览代码

[Web] add f2b_banlist endpoint

FreddleSpl0it 2 年之前
父节点
当前提交
e2e8fbe313

+ 2 - 0
data/Dockerfiles/netfilter/server.py

@@ -16,6 +16,7 @@ import json
 import iptc
 import iptc
 import dns.resolver
 import dns.resolver
 import dns.exception
 import dns.exception
+import uuid
 
 
 while True:
 while True:
   try:
   try:
@@ -94,6 +95,7 @@ def verifyF2boptions(f2boptions):
   verifyF2boption(f2boptions,'retry_window', 600)
   verifyF2boption(f2boptions,'retry_window', 600)
   verifyF2boption(f2boptions,'netban_ipv4', 32)
   verifyF2boption(f2boptions,'netban_ipv4', 32)
   verifyF2boption(f2boptions,'netban_ipv6', 128)
   verifyF2boption(f2boptions,'netban_ipv6', 128)
+  verifyF2boption(f2boptions,'banlist_id', str(uuid.uuid4()))
 
 
 def verifyF2boption(f2boptions, f2boption, f2bdefault):
 def verifyF2boption(f2boptions, f2boption, f2bdefault):
   f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
   f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault

+ 5 - 1
data/web/admin.php

@@ -85,6 +85,8 @@ $cors_settings = cors('get');
 $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allowed_origins']);
 $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allowed_origins']);
 $cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
 $cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
 
 
+$f2b_data = fail2ban('get');
+
 $template = 'admin.twig';
 $template = 'admin.twig';
 $template_data = [
 $template_data = [
   'tfa_data' => $tfa_data,
   'tfa_data' => $tfa_data,
@@ -101,7 +103,8 @@ $template_data = [
   'domains' => $domains,
   'domains' => $domains,
   'all_domains' => $all_domains,
   'all_domains' => $all_domains,
   'mailboxes' => $mailboxes,
   'mailboxes' => $mailboxes,
-  'f2b_data' => fail2ban('get'),
+  'f2b_data' => $f2b_data,
+  'f2b_banlist_url' => getBaseUrl() . "/api/v1/get/fail2ban/banlist/" . $f2b_data['banlist_id'],
   'q_data' => quarantine('settings'),
   'q_data' => quarantine('settings'),
   'qn_data' => quota_notification('get'),
   'qn_data' => quota_notification('get'),
   'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'),
   'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'),
@@ -112,6 +115,7 @@ $template_data = [
   'password_complexity' => password_complexity('get'),
   'password_complexity' => password_complexity('get'),
   'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
   'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
   'cors_settings' => $cors_settings,
   'cors_settings' => $cors_settings,
+  'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
   'lang_admin' => json_encode($lang['admin']),
   'lang_admin' => json_encode($lang['admin']),
   'lang_datatables' => json_encode($lang['datatables'])
   'lang_datatables' => json_encode($lang['datatables'])
 ];
 ];

+ 64 - 1
data/web/inc/functions.fail2ban.inc.php

@@ -1,5 +1,5 @@
 <?php
 <?php
-function fail2ban($_action, $_data = null) {
+function fail2ban($_action, $_data = null, $_extra = null) {
   global $redis;
   global $redis;
   $_data_log = $_data;
   $_data_log = $_data;
   switch ($_action) {
   switch ($_action) {
@@ -329,5 +329,68 @@ function fail2ban($_action, $_data = null) {
         'msg' => 'f2b_modified'
         'msg' => 'f2b_modified'
       );
       );
     break;
     break;
+    case 'banlist':
+      try {
+        $f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true);
+      } 
+      catch (RedisException $e) {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
+          'msg' => array('redis_error', $e)
+        );
+        return false;
+      }
+      if (is_array($_extra)) {
+        $_extra = $_extra[0];
+      }
+      if ($_extra != $f2b_options['banlist_id']){
+        return false;
+      }
+
+      switch ($_data) {
+        case 'get':
+          try {
+            $bl = $redis->hGetAll('F2B_BLACKLIST');
+            $active_bans = $redis->hGetAll('F2B_ACTIVE_BANS');
+          } 
+          catch (RedisException $e) {
+            $_SESSION['return'][] = array(
+              'type' => 'danger',
+              'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
+              'msg' => array('redis_error', $e)
+            );
+            return false;
+          }
+          $banlist = implode("\n", array_merge(array_keys($bl), array_keys($active_bans)));
+          return $banlist;
+        break;
+        case 'refresh':
+          if ($_SESSION['mailcow_cc_role'] != "admin") {
+            return false;
+          }
+
+          $f2b_options['banlist_id'] = uuid4();
+          try {
+            $redis->Set('F2B_OPTIONS', json_encode($f2b_options));
+          } 
+          catch (RedisException $e) {
+            $_SESSION['return'][] = array(
+              'type' => 'danger',
+              'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
+              'msg' => array('redis_error', $e)
+            );
+            return false;
+          }
+
+          $_SESSION['return'][] = array(
+            'type' => 'success',
+            'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
+            'msg' => 'f2b_banlist_refreshed'
+          );
+          return true;
+        break;
+      }
+    break;
   }
   }
 }
 }

+ 15 - 0
data/web/inc/functions.inc.php

@@ -2246,6 +2246,21 @@ function cors($action, $data = null) {
     break;
     break;
   }
   }
 }
 }
+function getBaseURL() {
+  $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
+  $host = $_SERVER['HTTP_HOST'];
+  $base_url = $protocol . '://' . $host;
+
+  return $base_url;
+}
+function uuid4() {
+  $data = openssl_random_pseudo_bytes(16);
+
+  $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
+  $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
+
+  return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
+}
 
 
 function get_logs($application, $lines = false) {
 function get_logs($application, $lines = false) {
   if ($lines === false) {
   if ($lines === false) {

+ 8 - 0
data/web/js/build/013-mailcow.js

@@ -371,3 +371,11 @@ function addTag(tagAddElem, tag = null){
   $(tagValuesElem).val(JSON.stringify(value_tags));
   $(tagValuesElem).val(JSON.stringify(value_tags));
   $(tagInputElem).val('');
   $(tagInputElem).val('');
 }
 }
+function copyToClipboard(id) {
+  var copyText = document.getElementById(id);
+  copyText.select();
+  copyText.setSelectionRange(0, 99999);
+  // only works with https connections
+  navigator.clipboard.writeText(copyText.value);
+  mailcow_alert_box(lang.copy_to_clipboard, "success");
+}

+ 20 - 1
data/web/json_api.php

@@ -503,6 +503,15 @@ if (isset($_GET['query'])) {
           print(json_encode($getArgs));
           print(json_encode($getArgs));
           $_SESSION['challenge'] = $WebAuthn->getChallenge();
           $_SESSION['challenge'] = $WebAuthn->getChallenge();
           return;
           return;
+        break;          
+        case "fail2ban":
+          if (!isset($_SESSION['mailcow_cc_role'])){
+            switch ($object) {
+              case 'banlist':
+                echo fail2ban('banlist', 'get', $extra);
+              break;
+            }
+          }
         break;
         break;
       }
       }
       if (isset($_SESSION['mailcow_cc_role'])) {
       if (isset($_SESSION['mailcow_cc_role'])) {
@@ -1324,6 +1333,9 @@ if (isset($_GET['query'])) {
           break;
           break;
           case "fail2ban":
           case "fail2ban":
             switch ($object) {
             switch ($object) {
+              case 'banlist':
+                echo fail2ban('banlist', 'get', $extra);
+              break;
               default:
               default:
                 $data = fail2ban('get');
                 $data = fail2ban('get');
                 process_get_return($data);
                 process_get_return($data);
@@ -1930,7 +1942,14 @@ if (isset($_GET['query'])) {
           process_edit_return(fwdhost('edit', array_merge(array('fwdhost' => $items), $attr)));
           process_edit_return(fwdhost('edit', array_merge(array('fwdhost' => $items), $attr)));
         break;
         break;
         case "fail2ban":
         case "fail2ban":
-          process_edit_return(fail2ban('edit', array_merge(array('network' => $items), $attr)));
+          switch ($object) {
+            case 'banlist':
+              process_edit_return(fail2ban('banlist', 'refresh', $items));
+            break;
+            default:
+              process_edit_return(fail2ban('edit', array_merge(array('network' => $items), $attr)));
+            break;
+          }
         break;
         break;
         case "ui_texts":
         case "ui_texts":
           process_edit_return(customize('edit', 'ui_texts', $attr));
           process_edit_return(customize('edit', 'ui_texts', $attr));

+ 2 - 0
data/web/lang/lang.de-de.json

@@ -147,6 +147,7 @@
         "change_logo": "Logo ändern",
         "change_logo": "Logo ändern",
         "configuration": "Konfiguration",
         "configuration": "Konfiguration",
         "convert_html_to_text": "Konvertiere HTML zu reinem Text",
         "convert_html_to_text": "Konvertiere HTML zu reinem Text",
+        "copy_to_clipboard": "Text wurde in die Zwischenablage kopiert!",
         "cors_settings": "CORS Einstellungen",
         "cors_settings": "CORS Einstellungen",
         "credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.",
         "credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.",
         "customer_id": "Kunde",
         "customer_id": "Kunde",
@@ -1019,6 +1020,7 @@
         "domain_removed": "Domain %s wurde entfernt",
         "domain_removed": "Domain %s wurde entfernt",
         "dovecot_restart_success": "Dovecot wurde erfolgreich neu gestartet",
         "dovecot_restart_success": "Dovecot wurde erfolgreich neu gestartet",
         "eas_reset": "ActiveSync Gerät des Benutzers %s wurde zurückgesetzt",
         "eas_reset": "ActiveSync Gerät des Benutzers %s wurde zurückgesetzt",
+        "f2b_banlist_refreshed": "Banlist ID wurde erfolgreich erneuert.",
         "f2b_modified": "Änderungen an Fail2ban-Parametern wurden gespeichert",
         "f2b_modified": "Änderungen an Fail2ban-Parametern wurden gespeichert",
         "forwarding_host_added": "Weiterleitungs-Host %s wurde hinzugefügt",
         "forwarding_host_added": "Weiterleitungs-Host %s wurde hinzugefügt",
         "forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt",
         "forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt",

+ 2 - 0
data/web/lang/lang.en-gb.json

@@ -151,6 +151,7 @@
         "change_logo": "Change logo",
         "change_logo": "Change logo",
         "configuration": "Configuration",
         "configuration": "Configuration",
         "convert_html_to_text": "Convert HTML to plain text",
         "convert_html_to_text": "Convert HTML to plain text",
+        "copy_to_clipboard": "Text copied to clipboard!",
         "cors_settings": "CORS Settings",
         "cors_settings": "CORS Settings",
         "credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
         "credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
         "customer_id": "Customer ID",
         "customer_id": "Customer ID",
@@ -1028,6 +1029,7 @@
         "domain_removed": "Domain %s has been removed",
         "domain_removed": "Domain %s has been removed",
         "dovecot_restart_success": "Dovecot was restarted successfully",
         "dovecot_restart_success": "Dovecot was restarted successfully",
         "eas_reset": "ActiveSync devices for user %s were reset",
         "eas_reset": "ActiveSync devices for user %s were reset",
+        "f2b_banlist_refreshed": "Banlist ID has been successfully refreshed.",
         "f2b_modified": "Changes to Fail2ban parameters have been saved",
         "f2b_modified": "Changes to Fail2ban parameters have been saved",
         "forwarding_host_added": "Forwarding host %s has been added",
         "forwarding_host_added": "Forwarding host %s has been added",
         "forwarding_host_removed": "Forwarding host %s has been removed",
         "forwarding_host_removed": "Forwarding host %s has been removed",

+ 9 - 0
data/web/templates/admin/tab-config-f2b.twig

@@ -90,6 +90,15 @@
       {% if not f2b_data.active_bans and not f2b_data.perm_bans %}
       {% if not f2b_data.active_bans and not f2b_data.perm_bans %}
         <i>{{ lang.admin.no_active_bans }}</i>
         <i>{{ lang.admin.no_active_bans }}</i>
       {% endif %}
       {% endif %}
+      <form class="form-inline" data-id="f2b_banlist" role="form" method="post">
+        <div class="input-group mb-3">
+          <input type="text" class="form-control" aria-label="Banlist url" value="{{ f2b_banlist_url}}" id="banlist_url">
+          {% if is_https %}
+          <button class="btn btn-secondary" type="button" onclick="copyToClipboard('banlist_url')"><i class="bi bi-clipboard"></i></button>
+          {% endif %}
+          <button class="btn btn-secondary" type="button" data-action="edit_selected" data-item="{{ f2b_data.banlist_id }}" data-id="f2b_banlist" data-api-url='edit/fail2ban/banlist' data-api-attr='{}'><i class="bi bi-arrow-clockwise"></i></button>
+        </div>
+      </form>
       {% for active_ban in f2b_data.active_bans %}
       {% for active_ban in f2b_data.active_bans %}
         <p>
         <p>
           <span class="badge fs-5 bg-info" style="padding:4px;font-size:85%;">
           <span class="badge fs-5 bg-info" style="padding:4px;font-size:85%;">