From 0fda6a51a6aa5f637aa7630a91f1c518997efc28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rajmund=20Hru=C5=A1ka?= <rajmund.hruska@cesnet.cz>
Date: Tue, 3 Aug 2021 16:25:41 +0200
Subject: [PATCH] Feature: Use fallback for emails with no recipients. (Redmine
 issue: #6227)

---
 conf/core/reporting.json.conf    |  1 +
 lib/mentat/const.py              |  2 ++
 lib/mentat/datatype/internal.py  | 13 ++++++++-----
 lib/mentat/module/netmngr.py     | 12 ++++++++++++
 lib/mentat/module/reporter.py    |  1 +
 lib/mentat/reports/event.py      |  8 +++++---
 lib/mentat/reports/test_event.py |  1 +
 7 files changed, 30 insertions(+), 8 deletions(-)

diff --git a/conf/core/reporting.json.conf b/conf/core/reporting.json.conf
index 14f8c0766..897e18ece 100644
--- a/conf/core/reporting.json.conf
+++ b/conf/core/reporting.json.conf
@@ -7,6 +7,7 @@
         "templates_dir": "/etc/mentat/templates/reporter",
         "event_classes_dir": "/etc/mentat/event_classes",
         "mail_admin": "root",
+        "fallback": ["abuse@cesnet.cz"],
 
         # Additional variables that can be used inside the report templates.
         #   default: {}
diff --git a/lib/mentat/const.py b/lib/mentat/const.py
index 744ba7922..a7fec6b16 100644
--- a/lib/mentat/const.py
+++ b/lib/mentat/const.py
@@ -120,6 +120,8 @@ CKEY_CORE_REPORTER_TEMPLATEVARS = 'template_vars'
 """Name of the configuration subkey key for ``template vars`` configuration in ``core reporter`` configurations."""
 CKEY_CORE_REPORTER_EVENTCLASSESDIR = 'event_classes_dir'
 """Name of the configuration subkey key for ``event classes dir`` configuration in ``core reporter`` configurations."""
+CKEY_CORE_REPORTER_FALLBACK = 'fallback'
+"""Name of the configuration subkey key for ``fallback`` configuration in ``core reporter`` configurations."""
 
 CKEY_CORE_INFORMANT = '__core__informant'
 """Name of the configuration key for ``core informant`` configurations."""
diff --git a/lib/mentat/datatype/internal.py b/lib/mentat/datatype/internal.py
index ea5aa87ef..ef3948ebf 100644
--- a/lib/mentat/datatype/internal.py
+++ b/lib/mentat/datatype/internal.py
@@ -342,17 +342,14 @@ def t_network_record(val, source = None):
     if 'abuse_group' in val:
         record['abuse_group'] = val['abuse_group']
     elif 'resolved_abuses' in val:
-        for severity in 'low', 'medium', 'high', 'critical':
+        for severity in 'fallback', 'low', 'medium', 'high', 'critical':
             if severity in val['resolved_abuses']:
                 record['emails_{}'.format(severity)] = val['resolved_abuses'][severity]
 
         # compute resulting abuse group from all emails
         emails = []
-        for severity in 'emails_low', 'emails_medium', 'emails_high', 'emails_critical':
+        for severity in 'emails_fallback', 'emails_low', 'emails_medium', 'emails_high', 'emails_critical':
             if severity in record:
-                # TODO: this probably won't be necessary, after the issue is solved elsewhere
-                if len(record[severity]) > 1 and 'abuse@cesnet.cz' in record[severity]:
-                    record[severity].remove('abuse@cesnet.cz')
                 emails.extend(record[severity])
         if emails:
             record['abuse_group'] = ','.join(sorted(map(str.lower, list(set(emails)))))
@@ -487,6 +484,9 @@ def typedef_network_record_ip4(flavour, list_flavour, addon = None):
         'emails': {
             'type': list_flavour['String']
         },
+        'emails_fallback': {
+            'type': list_flavour['String']
+        },
         'emails_low': {
             'type': list_flavour['String']
         },
@@ -570,6 +570,9 @@ def typedef_network_record_ip6(flavour, list_flavour, addon = None):
         'emails': {
             'type': list_flavour['String']
         },
+        'emails_fallback': {
+            'type': list_flavour['String']
+        },
         'emails_low': {
             'type': list_flavour['String']
         },
diff --git a/lib/mentat/module/netmngr.py b/lib/mentat/module/netmngr.py
index 53d71a34e..f2706acde 100644
--- a/lib/mentat/module/netmngr.py
+++ b/lib/mentat/module/netmngr.py
@@ -452,6 +452,12 @@ class MentatNetmngrScript(mentat.script.fetcher.FetcherScript):
             emails = {'low': [], 'medium': [], 'high': [], 'critical': []}
             for nwrkey in wi_file_data[abuse_group]:
                 for network in wi_file_data[abuse_group][nwrkey]:
+                    # Check that the fallback defined in the whois_file is the same as the global one.
+                    if 'emails_fallback' in network:
+                        system_fallback = self.config[mentat.const.CKEY_CORE_REPORTER][mentat.const.CKEY_CORE_REPORTER_FALLBACK]
+                        if not set(map(str.lower, system_fallback)) == set(map(str.lower, network['emails_fallback'])):
+                            self.logger.error("Fallback provided by whois file is different from the system fallback defined in reporting configuration.")
+                        continue
                     for severity in ['low', 'medium', 'high', 'critical']:
                         emails[severity].append(sorted(map(str.lower, list(set(network.get('emails_{}'.format(severity), []))))))
 
@@ -507,6 +513,9 @@ class MentatNetmngrScript(mentat.script.fetcher.FetcherScript):
 
                 checked_group_name = False
                 for network in [network for networks in wi_file_data[group_name].values() for network in networks]:
+                    # Do not create networks where fallback option is present.
+                    if 'emails_fallback' in network:
+                        continue
                     # If there are multiple emails in this abuse group or lowest severity is not low then save
                     # emails in the reporting settings. By default, if the group in the database doesn't have
                     # any emails set in the reporting settings then the name of the abuse group is used as
@@ -587,6 +596,9 @@ class MentatNetmngrScript(mentat.script.fetcher.FetcherScript):
         """
         gkey = '{}::{}'.format(group.name, group.source)
         for net in networks:
+            # Do not create networks where fallback option is present.
+            if 'emails_fallback' in net:
+                continue
             nkey = '{}::{}'.format(net['network'], net['source'])
             index = self._get_index_of_network(net, group.networks)
             if index == -1:
diff --git a/lib/mentat/module/reporter.py b/lib/mentat/module/reporter.py
index ba8fd8fe6..804fb647d 100644
--- a/lib/mentat/module/reporter.py
+++ b/lib/mentat/module/reporter.py
@@ -360,6 +360,7 @@ class MentatReporterScript(mentat.script.fetcher.FetcherScript):
                 self.logger,
                 self.config[self.CORECFG_REPORTER][self.CONFIG_REPORTS_DIR],
                 self.config[self.CORECFG_REPORTER][self.CONFIG_TEMPLATES_DIR],
+                self.config[mentat.const.CKEY_CORE_REPORTER][mentat.const.CKEY_CORE_REPORTER_FALLBACK],
                 mentat.const.DFLT_REPORTING_LOCALE,
                 mentat.const.DFLT_REPORTING_TIMEZONE,
                 self.eventservice,
diff --git a/lib/mentat/reports/event.py b/lib/mentat/reports/event.py
index 0d011db52..912a6f0f6 100644
--- a/lib/mentat/reports/event.py
+++ b/lib/mentat/reports/event.py
@@ -145,7 +145,7 @@ class EventReporter(BaseReporter):
     Implementation of reporting class providing Mentat event reports.
     """
 
-    def __init__(self, logger, reports_dir, templates_dir, locale, timezone, eventservice, sqlservice, mailer, event_classes_dir, groups_dict, settings_dict, whoismodule, thresholding = True):
+    def __init__(self, logger, reports_dir, templates_dir, fallback, locale, timezone, eventservice, sqlservice, mailer, event_classes_dir, groups_dict, settings_dict, whoismodule, thresholding = True):
         self.translations_dir = ";".join([os.path.join(event_classes_dir, event_class, "translations")
                                           for event_class in os.listdir(event_classes_dir)
                                           if os.path.isdir(os.path.join(event_classes_dir, event_class))])
@@ -158,6 +158,7 @@ class EventReporter(BaseReporter):
 
         self.event_classes_data = {}
         self.event_classes_dir = event_classes_dir
+        self.fallback = fallback
 
         self.filter_parser   = PynspectFilterParser()
         self.filter_compiler = IDEAFilterCompiler()
@@ -997,9 +998,10 @@ class EventReporter(BaseReporter):
                 i -= 1
         cc = list(set(cc))
 
+        # Use fallback option if no email addresses are found for the given severity.
         if not to:
-            #TODO: fallback option
-            return
+            to = self.fallback
+            self.logger.info("No email addresses found for the given severity, using fallback: %s", to)
 
         # Common report email headers.
         report_msg_headers = {
diff --git a/lib/mentat/reports/test_event.py b/lib/mentat/reports/test_event.py
index 46e5e981c..2638dd4fa 100644
--- a/lib/mentat/reports/test_event.py
+++ b/lib/mentat/reports/test_event.py
@@ -184,6 +184,7 @@ class TestMentatReportsEvent(unittest.TestCase):
             Mock(),
             REPORTS_DIR,
             os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../conf/templates/reporter')),
+            [],
             'en',
             'UTC',
             self.eventstorage,
-- 
GitLab