55# -*- coding: utf-8 -*-
66import datetime
77import json
8+ import re
89from pathlib import Path
910from uuid import uuid4
10- import re
1111
1212from intelmq import VAR_STATE_PATH
1313from intelmq .lib .bot import OutputBot
1414from intelmq .lib .exceptions import MissingDependencyError
15+ from intelmq .lib .mixins import CacheMixin
1516from intelmq .lib .utils import parse_relative
1617
1718try :
2021except ImportError :
2122 # catching SyntaxError because of https://github.com/MISP/PyMISP/issues/501
2223 MISPEvent = None
23- import_fail_reason = 'import'
24- except SyntaxError :
25- # catching SyntaxError because of https://github.com/MISP/PyMISP/issues/501
26- MISPEvent = None
27- import_fail_reason = 'syntax'
28-
24+ import_fail_reason = "import"
2925
30- # NOTE: This module is compatible with Python 3.6+
3126
32-
33- class MISPFeedOutputBot (OutputBot ):
27+ class MISPFeedOutputBot (OutputBot , CacheMixin ):
3428 """Generate an output in the MISP Feed format"""
29+
3530 interval_event : str = "1 hour"
31+ delay_save_event_count : int = None
3632 misp_org_name = None
3733 misp_org_uuid = None
3834 output_dir : str = f"{ VAR_STATE_PATH } mispfeed-output" # TODO: should be path
@@ -46,13 +42,8 @@ def check_output_dir(dirname):
4642 return True
4743
4844 def init (self ):
49- if MISPEvent is None and import_fail_reason == 'syntax' :
50- raise MissingDependencyError ("pymisp" ,
51- version = '>=2.4.117.3' ,
52- additional_text = "Python versions below 3.6 are "
53- "only supported by pymisp <= 2.4.119.1." )
54- elif MISPEvent is None :
55- raise MissingDependencyError ('pymisp' , version = '>=2.4.117.3' )
45+ if MISPEvent is None :
46+ raise MissingDependencyError ("pymisp" , version = ">=2.4.117.3" )
5647
5748 self .current_event = None
5849
@@ -72,59 +63,90 @@ def init(self):
7263 try :
7364 with (self .output_dir / '.current' ).open () as f :
7465 self .current_file = Path (f .read ())
75- self .current_event = MISPEvent ()
76- self .current_event .load_file (self .current_file )
77-
78- last_min_time , last_max_time = re .findall ('IntelMQ event (.*) - (.*)' , self .current_event .info )[0 ]
79- last_min_time = datetime .datetime .strptime (last_min_time , '%Y-%m-%dT%H:%M:%S.%f' )
80- last_max_time = datetime .datetime .strptime (last_max_time , '%Y-%m-%dT%H:%M:%S.%f' )
81- if last_max_time < datetime .datetime .now ():
82- self .min_time_current = datetime .datetime .now ()
83- self .max_time_current = self .min_time_current + self .timedelta
84- self .current_event = None
85- else :
86- self .min_time_current = last_min_time
87- self .max_time_current = last_max_time
66+
67+ if self .current_file .exists ():
68+ self .current_event = MISPEvent ()
69+ self .current_event .load_file (self .current_file )
70+
71+ last_min_time , last_max_time = re .findall (
72+ "IntelMQ event (.*) - (.*)" , self .current_event .info
73+ )[0 ]
74+ last_min_time = datetime .datetime .strptime (
75+ last_min_time , "%Y-%m-%dT%H:%M:%S.%f"
76+ )
77+ last_max_time = datetime .datetime .strptime (
78+ last_max_time , "%Y-%m-%dT%H:%M:%S.%f"
79+ )
80+ if last_max_time < datetime .datetime .now ():
81+ self .min_time_current = datetime .datetime .now ()
82+ self .max_time_current = self .min_time_current + self .timedelta
83+ self .current_event = None
84+ else :
85+ self .min_time_current = last_min_time
86+ self .max_time_current = last_max_time
8887 except :
89- self .logger .exception ("Loading current event %s failed. Skipping it." , self .current_event )
88+ self .logger .exception (
89+ "Loading current event %s failed. Skipping it." , self .current_event
90+ )
9091 self .current_event = None
9192 else :
9293 self .min_time_current = datetime .datetime .now ()
9394 self .max_time_current = self .min_time_current + self .timedelta
9495
9596 def process (self ):
96-
9797 if not self .current_event or datetime .datetime .now () > self .max_time_current :
9898 self .min_time_current = datetime .datetime .now ()
9999 self .max_time_current = self .min_time_current + self .timedelta
100100 self .current_event = MISPEvent ()
101- self .current_event .info = ('IntelMQ event {begin} - {end}'
102- '' .format (begin = self .min_time_current .isoformat (),
103- end = self .max_time_current .isoformat ()))
101+ self .current_event .info = "IntelMQ event {begin} - {end}" "" .format (
102+ begin = self .min_time_current .isoformat (),
103+ end = self .max_time_current .isoformat (),
104+ )
104105 self .current_event .set_date (datetime .date .today ())
105106 self .current_event .Orgc = self .misp_org
106107 self .current_event .uuid = str (uuid4 ())
107- self .current_file = self .output_dir / f' { self .current_event .uuid } .json'
108- with (self .output_dir / ' .current' ).open ('w' ) as f :
108+ self .current_file = self .output_dir / f" { self .current_event .uuid } .json"
109+ with (self .output_dir / " .current" ).open ("w" ) as f :
109110 f .write (str (self .current_file ))
110111
112+ # On startup or when timeout occurs, clean the queue to ensure we do not
113+ # keep events forever because there was not enough generated
114+ self ._generate_feed ()
115+
111116 event = self .receive_message ().to_dict (jsondict_as_string = True )
112117
113- obj = self .current_event .add_object (name = 'intelmq_event' )
114- for object_relation , value in event .items ():
118+ cache_size = None
119+ if self .delay_save_event_count :
120+ cache_size = self .cache_put (event )
121+
122+ if cache_size is None :
123+ self ._generate_feed (event )
124+ elif cache_size >= self .delay_save_event_count :
125+ self ._generate_feed ()
126+
127+ self .acknowledge_message ()
128+
129+ def _add_message_to_feed (self , message : dict ):
130+ obj = self .current_event .add_object (name = "intelmq_event" )
131+ for object_relation , value in message .items ():
115132 try :
116133 obj .add_attribute (object_relation , value = value )
117134 except NewAttributeError :
118135 # This entry isn't listed in the harmonization file, ignoring.
119136 pass
120137
121- feed_output = self .current_event .to_feed (with_meta = False )
138+ def _generate_feed (self , message : dict = None ):
139+ if message :
140+ self ._add_message_to_feed (message )
141+
142+ while message := self .cache_pop ():
143+ self ._add_message_to_feed (message )
122144
123- with self .current_file .open ('w' ) as f :
145+ feed_output = self .current_event .to_feed (with_meta = False )
146+ with self .current_file .open ("w" ) as f :
124147 json .dump (feed_output , f )
125148
126149 feed_meta_generator (self .output_dir )
127- self .acknowledge_message ()
128150
129151 @staticmethod
130152 def check (parameters ):
0 commit comments