diff --git a/scripts/base/frameworks/file-analysis/__load__.bro b/scripts/base/frameworks/file-analysis/__load__.bro new file mode 100644 index 0000000000..783797e17b --- /dev/null +++ b/scripts/base/frameworks/file-analysis/__load__.bro @@ -0,0 +1 @@ +@load ./main.bro diff --git a/scripts/base/frameworks/file-analysis/main.bro b/scripts/base/frameworks/file-analysis/main.bro new file mode 100644 index 0000000000..58e62c755d --- /dev/null +++ b/scripts/base/frameworks/file-analysis/main.bro @@ -0,0 +1,81 @@ +##! TODO add some comments here + +@load base/file_analysis.bif + +# TODO: do logging here? +@load base/frameworks/logging + +module FileAnalysis; + +export { + redef enum Log::ID += { + ## Logging stream for file analysis. + LOG + }; + + ## The default buffer size used to reassemble files. + # TODO: what's a reasonable default? + const default_reassembly_buffer_size: count = 1024*1024 &redef; + + ## The default buffer size used for storing the beginning of files. + # TODO: what's a reasonable default? + const default_bof_buffer_size: count = 256 &redef; + + ## The default amount of time file analysis will wait for new file data + ## before giving up. + ## TODO: what's a reasonable default? + #const default_timeout_interval: interval = 2 mins &redef; + const default_timeout_interval: interval = 10 sec &redef; + + ## The default amount of data that a user is allowed to extract + ## from a file to an event with the + ## :bro:see:`FileAnalysis::ACTION_DATA_EVENT` action. + ## TODO: what's a reasonable default? + const default_data_event_len: count = 1024*1024 &redef; + + ## Contains all metadata related to the analysis of a given file, some + ## of which is logged. + type Info: record { + ## Unique identifier associated with a single file. + file_id: string &log; + ## Unique identifier associated with the file if it was extracted + ## from a container file as part of the analysis. + parent_file_id: string &log &optional; + + ## The network protocol over which the file was transferred. + protocol: string &log &optional; + + ## The set of connections over which the file was transferred, + ## indicated by UID strings. + conn_uids: set[string] &log &optional; + ## The set of connections over which the file was transferred, + ## indicated by 5-tuples. + conn_ids: set[conn_id] &optional; + + ## Number of bytes provided to the file analysis engine for the file. + seen_bytes: count &log &default=0; + ## Total number of bytes that are supposed to comprise the file content. + total_bytes: count &log &optional; + + ## The number of not all-in-sequence bytes over the course of the + ## analysis that had to be discarded due to a reassembly buffer size + ## of *reassembly_buffer_size* being filled. + undelivered: count &default=0; + + ## The amount of time between receiving new data for this file that + ## the analysis engine will wait before giving up on it. + timeout_interval: interval &default=default_timeout_interval; + + } &redef; + + ## TODO: document + global policy: hook(trig: Trigger, info: Info); + + ## TODO: document + global postpone_timeout: function(file_id: string): bool; +} + +function postpone_timeout(file_id: string): bool + { + return __postpone_timeout(file_id); + } diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index e5365a9428..9a917621e0 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -2866,3 +2866,4 @@ const snaplen = 8192 &redef; @load base/frameworks/input +@load base/frameworks/file-analysis diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71adee9158..de7f9f5545 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -145,6 +145,7 @@ set(BIF_SRCS logging.bif input.bif event.bif + file_analysis.bif const.bif types.bif strings.bif @@ -328,6 +329,7 @@ set(bro_SRCS FTP.cc File.cc FileAnalyzer.cc + FileAnalysisManager.cc Finger.cc FlowSrc.cc Frag.cc diff --git a/src/DebugLogger.cc b/src/DebugLogger.cc index 3394486ff2..380f21aa5f 100644 --- a/src/DebugLogger.cc +++ b/src/DebugLogger.cc @@ -16,7 +16,7 @@ DebugLogger::Stream DebugLogger::streams[NUM_DBGS] = { { "notifiers", 0, false }, { "main-loop", 0, false }, { "dpd", 0, false }, { "tm", 0, false }, { "logging", 0, false }, {"input", 0, false }, - { "threading", 0, false } + { "threading", 0, false }, { "file_analysis", 0, false } }; DebugLogger::DebugLogger(const char* filename) diff --git a/src/DebugLogger.h b/src/DebugLogger.h index ca422072c5..5ce7230066 100644 --- a/src/DebugLogger.h +++ b/src/DebugLogger.h @@ -26,6 +26,7 @@ enum DebugStream { DBG_LOGGING, // Logging streams DBG_INPUT, // Input streams DBG_THREADING, // Threading system + DBG_FILE_ANALYSIS, // File analysis NUM_DBGS // Has to be last }; diff --git a/src/FileAnalysisManager.cc b/src/FileAnalysisManager.cc new file mode 100644 index 0000000000..1bf72cf84f --- /dev/null +++ b/src/FileAnalysisManager.cc @@ -0,0 +1,276 @@ +#include + +#include "FileAnalysisManager.h" +#include "util.h" + +using namespace file_analysis; + +static TableVal* empty_conn_id_set() + { + TypeList* set_index = new TypeList(conn_id); + set_index->Append(conn_id->Ref()); + return new TableVal(new SetType(set_index, 0)); + } + +static StringVal* get_conn_uid_val(Connection* conn) + { + char tmp[20]; + if ( ! conn->GetUID() ) + conn->SetUID(calculate_unique_id()); + return new StringVal(uitoa_n(conn->GetUID(), tmp, sizeof(tmp), 62)); + } + +static RecordVal* get_conn_id_val(const Connection* conn) + { + RecordVal* v = new RecordVal(conn_id); + v->Assign(0, new AddrVal(conn->OrigAddr())); + v->Assign(1, new PortVal(ntohs(conn->OrigPort()), conn->ConnTransport())); + v->Assign(2, new AddrVal(conn->RespAddr())); + v->Assign(3, new PortVal(ntohs(conn->RespPort()), conn->ConnTransport())); + return v; + } + +int Info::file_id_idx = -1; +int Info::parent_file_id_idx = -1; +int Info::protocol_idx = -1; +int Info::conn_uids_idx = -1; +int Info::conn_ids_idx = -1; +int Info::seen_bytes_idx = -1; +int Info::total_bytes_idx = -1; +int Info::undelivered_idx = -1; +int Info::timeout_interval_idx = -1; + +Info::Info(const string& file_id, Connection* conn, AnalyzerTag::Tag at) + : val(0), last_activity_time(network_time), postpone_timeout(false) + { + DBG_LOG(DBG_FILE_ANALYSIS, "Creating new Info object %s", file_id.c_str()); + + if ( file_id_idx == -1 ) + { + file_id_idx = Idx("file_id"); + parent_file_id_idx = Idx("parent_file_id"); + protocol_idx = Idx("protocol"); + conn_uids_idx = Idx("conn_uids"); + conn_ids_idx = Idx("conn_ids"); + seen_bytes_idx = Idx("seen_bytes"); + total_bytes_idx = Idx("total_bytes"); + undelivered_idx = Idx("undelivered"); + timeout_interval_idx = Idx("timeout_interval"); + } + + val = new RecordVal(BifType::Record::FileAnalysis::Info); + val->Assign(file_id_idx, new StringVal(file_id.c_str())); + + UpdateConnectionFields(conn); + + if ( at != AnalyzerTag::Error ) + val->Assign(protocol_idx, new StringVal(Analyzer::GetTagName(at))); + + ScheduleInactivityTimer(); + Manager::EvaluatePolicy(BifEnum::FileAnalysis::TRIGGER_NEW, this); + } + +Info::~Info() + { + DBG_LOG(DBG_FILE_ANALYSIS, "Destroying Info object %s", FileID().c_str()); + Unref(val); + } + +void Info::UpdateConnectionFields(Connection* conn) + { + if ( ! conn ) return; + + Val* conn_uids = val->Lookup(conn_uids_idx); + Val* conn_ids = val->Lookup(conn_ids_idx); + if ( ! conn_uids ) + val->Assign(conn_uids_idx, conn_uids = new TableVal(string_set)); + if ( ! conn_ids ) + val->Assign(conn_ids_idx, conn_ids = empty_conn_id_set()); + + conn_uids->AsTableVal()->Assign(get_conn_uid_val(conn), 0); + conn_ids->AsTableVal()->Assign(get_conn_id_val(conn), 0); + } + +int Info::Idx(const string& field) + { + int rval = BifType::Record::FileAnalysis::Info->FieldOffset(field.c_str()); + if ( rval < 0 ) + reporter->InternalError("Unkown FileAnalysis::Info field: %s", + field.c_str()); + return rval; + } + +double Info::TimeoutInterval() const + { + return val->LookupWithDefault(timeout_interval_idx)->AsInterval(); + } + +string Info::FileID() const + { + return val->Lookup(file_id_idx)->AsString()->CheckString(); + } + +void Info::SetTotalBytes(uint64 size) + { + val->Assign(total_bytes_idx, new Val(size, TYPE_COUNT)); + + if ( val->LookupWithDefault(seen_bytes_idx)->AsCount() >= size ) + { + Manager::EvaluatePolicy(BifEnum::FileAnalysis::TRIGGER_DONE, this); + file_mgr->Remove(FileID()); + } + } + +void Info::ScheduleInactivityTimer() const + { + timer_mgr->Add(new InfoTimer(network_time, FileID(), TimeoutInterval())); + } + +void InfoTimer::Dispatch(double t, int is_expire) + { + Info* info = file_mgr->Lookup(file_id); + + if ( ! info ) return; + + double last_active = info->LastActivityTime(); + double inactive_time = t > last_active ? t - last_active : 0.0; + + DBG_LOG(DBG_FILE_ANALYSIS, "Checking inactivity for %s, last active at %f, " + "inactive for %f", file_id.c_str(), last_active, inactive_time); + + if ( last_active == 0.0 ) + { + // was created when network_time was zero, so re-schedule w/ valid time + info->UpdateLastActivityTime(); + info->ScheduleInactivityTimer(); + return; + } + + if ( inactive_time >= info->TimeoutInterval() ) + file_mgr->Timeout(file_id); + else if ( ! is_expire ) + info->ScheduleInactivityTimer(); + } + +Manager::Manager() + { + } + +Manager::~Manager() + { + Terminate(); + } + +void Manager::Terminate() + { + vector keys; + for ( FileMap::iterator it = file_map.begin(); it != file_map.end(); ++it ) + keys.push_back(it->first); + for ( size_t i = 0; i < keys.size(); ++i ) + Timeout(keys[i], true); + } + +void Manager::DataIn(const string& file_id, const u_char* data, uint64 len, + uint64 offset, Connection* conn, AnalyzerTag::Tag at) + { + Info* info = IDtoInfo(file_id, conn, at); + info->UpdateLastActivityTime(); + info->UpdateConnectionFields(conn); + // TODO: more stuff + } + +void Manager::DataIn(const string& file_id, const u_char* data, uint64 len, + Connection* conn, AnalyzerTag::Tag at) + { + Info* info = IDtoInfo(file_id, conn, at); + info->UpdateLastActivityTime(); + info->UpdateConnectionFields(conn); + // TODO: more stuff + } + +void Manager::SetSize(const string& file_id, uint64 size, + Connection* conn, AnalyzerTag::Tag at) + { + Info* info = IDtoInfo(file_id, conn, at); + info->UpdateLastActivityTime(); + info->UpdateConnectionFields(conn); + info->SetTotalBytes(size); + } + +void Manager::EvaluatePolicy(BifEnum::FileAnalysis::Trigger t, Info* info) + { + const ID* id = global_scope()->Lookup("FileAnalysis::policy"); + assert(id); + const Func* hook = id->ID_Val()->AsFunc(); + + val_list vl(2); + vl.append(new EnumVal(t, BifType::Enum::FileAnalysis::Trigger)); + vl.append(info->val->Ref()); + + info->postpone_timeout = false; + + Val* result = hook->Call(&vl); + Unref(result); + } + +bool Manager::PostponeTimeout(const string& file_id) const + { + Info* info = Lookup(file_id); + + if ( ! info ) return false; + + info->postpone_timeout = true; + return true; + } + +Info* Manager::IDtoInfo(const string& file_id, Connection* conn, + AnalyzerTag::Tag at) + { + Info* rval = file_map[file_id]; + if ( ! rval ) + rval = file_map[file_id] = new Info(file_id, conn, at); + return rval; + } + +Info* Manager::Lookup(const string& file_id) const + { + FileMap::const_iterator it = file_map.find(file_id); + + if ( it == file_map.end() ) return 0; + + return it->second; + } + +void Manager::Timeout(const string& file_id, bool is_terminating) + { + Info* info = Lookup(file_id); + + if ( ! info ) return; + + EvaluatePolicy(BifEnum::FileAnalysis::TRIGGER_TIMEOUT, info); + + if ( info->postpone_timeout && ! is_terminating ) + { + DBG_LOG(DBG_FILE_ANALYSIS, "Postpone file analysis timeout for %s", + info->FileID().c_str()); + info->UpdateLastActivityTime(); + info->ScheduleInactivityTimer(); + return; + } + + DBG_LOG(DBG_FILE_ANALYSIS, "File analysis timeout for %s", + info->FileID().c_str()); + + file_map.erase(file_id); + delete info; + } + +void Manager::Remove(const string& file_id) + { + FileMap::iterator it = file_map.find(file_id); + + if ( it == file_map.end() ) return; + + delete it->second; + file_map.erase(it); + } diff --git a/src/FileAnalysisManager.h b/src/FileAnalysisManager.h new file mode 100644 index 0000000000..106f67ef74 --- /dev/null +++ b/src/FileAnalysisManager.h @@ -0,0 +1,199 @@ +#ifndef FILE_ANALYSIS_MANAGER_H +#define FILE_ANALYSIS_MANAGER_H + +#include +#include + +#include "Conn.h" +#include "Analyzer.h" +#include "AnalyzerTags.h" +#include "Timer.h" +#include "Val.h" +#include "Reporter.h" + +namespace file_analysis { + +/** + * Wrapper class around \c FileAnalysis::Info record values from script layer. + */ +class Info { +public: + + ~Info(); + + /** + * @return value (seconds) of the "timeout_interval" field from #val record. + */ + double TimeoutInterval() const; + + /** + * @return value of the "file_id" field from #val record. + */ + string FileID() const; + + /** + * @return #last_activity_time + */ + double LastActivityTime() const { return last_activity_time; } + + /** + * Refreshes #last_activity_time with current network time. + */ + void UpdateLastActivityTime() { last_activity_time = network_time; } + + /** + * Set "total_bytes" field of #val record to \a size, check if "seen_bytes" + * is greater or equal to it, and evaluate \c FileAnalysis::policy if so. + */ + void SetTotalBytes(uint64 size); + + /** + * Create a timer to be dispatched after the amount of time indicated by + * the "timeout_interval" field of the #val record in order to check if + * #last_activity_time is old enough to timeout analysis of the file. + */ + void ScheduleInactivityTimer() const; + +protected: + + friend class Manager; + + /** + * Constructor; only file_analysis::Manager should be creating these. + */ + Info(const string& file_id, Connection* conn = 0, + AnalyzerTag::Tag at = AnalyzerTag::Error); + + /** + * Updates the "conn_ids" and "conn_uids" fields in #val record with the + * \c conn_id and UID taken from \a conn. + */ + void UpdateConnectionFields(Connection* conn); + + RecordVal* val; /**< \c FileAnalysis::Info from script layer. */ + double last_activity_time; /**< Time of last activity. */ + bool postpone_timeout; /**< Whether postponing timeout is requested. */ + + /** + * @return the field offset in #val record corresponding to \a field_name. + */ + static int Idx(const string& field_name); + + static int file_id_idx; + static int parent_file_id_idx; + static int protocol_idx; + static int conn_uids_idx; + static int conn_ids_idx; + static int seen_bytes_idx; + static int total_bytes_idx; + static int undelivered_idx; + static int timeout_interval_idx; +}; + +/** + * Timer to periodically check if file analysis for a given file is inative. + */ +class InfoTimer : public Timer { +public: + + InfoTimer(double t, const string& id, double interval) + : Timer(t + interval, TIMER_FILE_ANALYSIS_INACTIVITY), file_id(id) { } + + ~InfoTimer() { } + + /** + * Check inactivity of file_analysis::Info corresponding to #file_id, + * reschedule if active, else call file_analysis::Manager::Timeout. + */ + void Dispatch(double t, int is_expire); + +protected: + + string file_id; +}; + +/** + * Main entry point for interacting with file analysis. + */ +class Manager { +public: + + Manager(); + + ~Manager(); + + /** + * Times out any active file analysis to prepare for shutdown. + */ + void Terminate(); + + /** + * Pass in non-sequential file data. + */ + void DataIn(const string& file_id, const u_char* data, uint64 len, + uint64 offset, Connection* conn = 0, + AnalyzerTag::Tag at = AnalyzerTag::Error); + + /** + * Pass in sequential file data. + */ + void DataIn(const string& file_id, const u_char* data, uint64 len, + Connection* conn = 0, + AnalyzerTag::Tag at = AnalyzerTag::Error); + + /** + * Provide the expected number of bytes that comprise a file. + */ + void SetSize(const string& file_id, uint64 size, Connection* conn = 0, + AnalyzerTag::Tag at = AnalyzerTag::Error); + + /** + * Discard the file_analysis::Info object associated with \a file_id. + */ + void Remove(const string& file_id); + + /** + * If called during \c FileAnalysis::policy evaluation for a + * \c FileAnalysis::TRIGGER_TIMEOUT, requests deferral of analysis timeout. + */ + bool PostponeTimeout(const string& file_id) const; + + /** + * Calls the \c FileAnalysis::policy hook. + */ + static void EvaluatePolicy(BifEnum::FileAnalysis::Trigger t, Info* info); + +protected: + + friend class InfoTimer; + + typedef map FileMap; + + /** + * @return the Info object mapped to \a file_id. One is created if mapping + * doesn't exist. + */ + Info* IDtoInfo(const string& file_id, Connection* conn = 0, + AnalyzerTag::Tag at = AnalyzerTag::Error); + + /** + * @return the Info object mapped to \a file_id, or a null pointer if no + * mapping exists. + */ + + Info* Lookup(const string& file_id) const; + + /** + * Evaluate timeout policy for a file and remove the Info object mapped to + * \a file_id if needed. + */ + void Timeout(const string& file_id, bool is_terminating = ::terminating); + + FileMap file_map; /**< Map strings to \c FileAnalysis::Info records. */ +}; + +} // namespace file_analysis + +extern file_analysis::Manager* file_mgr; + +#endif diff --git a/src/Func.cc b/src/Func.cc index 9b94b15d97..a0d303dcf6 100644 --- a/src/Func.cc +++ b/src/Func.cc @@ -557,12 +557,14 @@ void builtin_error(const char* msg, BroObj* arg) #include "input.bif.func_h" #include "reporter.bif.func_h" #include "strings.bif.func_h" +#include "file_analysis.bif.func_h" #include "bro.bif.func_def" #include "logging.bif.func_def" #include "input.bif.func_def" #include "reporter.bif.func_def" #include "strings.bif.func_def" +#include "file_analysis.bif.func_def" void init_builtin_funcs() { @@ -578,6 +580,7 @@ void init_builtin_funcs() #include "input.bif.func_init" #include "reporter.bif.func_init" #include "strings.bif.func_init" +#include "file_analysis.bif.func_init" did_builtin_init = true; } diff --git a/src/NetVar.cc b/src/NetVar.cc index 1783130f34..99a4023062 100644 --- a/src/NetVar.cc +++ b/src/NetVar.cc @@ -246,6 +246,7 @@ StringVal* cmd_line_bpf_filter; #include "logging.bif.netvar_def" #include "input.bif.netvar_def" #include "reporter.bif.netvar_def" +#include "file_analysis.bif.netvar_def" void init_event_handlers() { @@ -308,6 +309,7 @@ void init_net_var() #include "logging.bif.netvar_init" #include "input.bif.netvar_init" #include "reporter.bif.netvar_init" +#include "file_analysis.bif.netvar_init" gtpv1_hdr_type = internal_type("gtpv1_hdr")->AsRecordType(); conn_id = internal_type("conn_id")->AsRecordType(); diff --git a/src/NetVar.h b/src/NetVar.h index 4bb2d2a7f9..7537b0793f 100644 --- a/src/NetVar.h +++ b/src/NetVar.h @@ -256,5 +256,6 @@ extern void init_net_var(); #include "logging.bif.netvar_h" #include "input.bif.netvar_h" #include "reporter.bif.netvar_h" +#include "file_analysis.bif.netvar_h" #endif diff --git a/src/Timer.h b/src/Timer.h index 310e72bdc9..615c8bf69a 100644 --- a/src/Timer.h +++ b/src/Timer.h @@ -22,6 +22,7 @@ enum TimerType { TIMER_CONN_INACTIVITY, TIMER_CONN_STATUS_UPDATE, TIMER_DNS_EXPIRE, + TIMER_FILE_ANALYSIS_INACTIVITY, TIMER_FRAG, TIMER_INCREMENTAL_SEND, TIMER_INCREMENTAL_WRITE, diff --git a/src/file_analysis.bif b/src/file_analysis.bif new file mode 100644 index 0000000000..f951d5cc8e --- /dev/null +++ b/src/file_analysis.bif @@ -0,0 +1,59 @@ +##! Internal functions and types used by the logging framework. + +module FileAnalysis; + +%%{ +#include "FileAnalysisManager.h" +%%} + +type Info: record; + +## An enumeration of possibly-interesting "events" that can occur over +## the course of analyzing files. The :bro:see:`FileAnalysis::policy` +## hook is called each time a trigger occurs. +enum Trigger %{ + ## Raised when any part of a new file is detected. + TRIGGER_NEW, + ## Raised when file analysis has likely seen a complete file. That + ## is when a number of bytes indicated by the *total_bytes* field of + ## :bro:see:`FileAnalysis::Info` have been processed. Note that + ## the *undelivered* field does not have to be zero for this to have + ## occurred. + TRIGGER_DONE, + ## Raised when file analysis for a given file is aborted due + ## to not seeing any data for it recently. Note that this doesn't + ## necessarily mean the full file wasn't seen (e.g. if the + ## :bro:see:`FileAnalysis::Info` record indicates the file *total_bytes* + ## isn't known). Use :bro:see:`FileAnalysis::postpone_timeout` + ## during a :bro:see:`FileAnalysis::policy` handler for this trigger to + ## defer the timeout until later. + TRIGGER_TIMEOUT, + ## Raised when the beginning of a file is detected. + TRIGGER_BOF, + ## Raised when the beginning of a file is available and that beginning + ## is at least the number of bytes indicated by the *bof_buffer_size* + ## field of :bro:see:`FileAnalysis::Info`. + TRIGGER_BOF_BUFFER_AVAIL, + ## Raised when the mime type of a file is matched based on magic + ## numbers. TODO: re-purposing protocols/http/file-ident.sig for + ## doing this is tricky since the signature engine doesn't expect + ## to be decoupled from connections, so figure out what work needs + ## done there. + TRIGGER_MIME_TYPE, + ## Raised when the end of a file is detected. If the file is not + ## being transferred linearly, then this doesn't have to mean the full + ## file has been transferred. + TRIGGER_EOF, + ## The reassembly buffer for the file filled and had to be discarded. + ## The *undelivered* field of :bro:see:`FileAnalysis::Info` will + ## indicate the number of bytes, if any, that were not all-in-sequence. + ## TODO: Is it possible to extend the reassembly buffer when "handling" + ## this trigger? + TRIGGER_REASSEMBLY_BUFFER_FULL, +%} + +function FileAnalysis::__postpone_timeout%(file_id: string%): bool + %{ + bool result = file_mgr->PostponeTimeout(file_id->CheckString()); + return new Val(result, TYPE_BOOL); + %} diff --git a/src/main.cc b/src/main.cc index 5999186240..75e5ae7e9b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -58,6 +58,8 @@ extern "C" void OPENSSL_add_all_algorithms_conf(void); #include "logging/Manager.h" #include "logging/writers/Ascii.h" +#include "FileAnalysisManager.h" + #include "binpac_bro.h" Brofiler brofiler; @@ -86,6 +88,7 @@ TimerMgr* timer_mgr; logging::Manager* log_mgr = 0; threading::Manager* thread_mgr = 0; input::Manager* input_mgr = 0; +file_analysis::Manager* file_mgr = 0; Stmt* stmts; EventHandlerPtr net_done = 0; RuleMatcher* rule_matcher = 0; @@ -319,6 +322,7 @@ void terminate_bro() mgr.Drain(); + file_mgr->Terminate(); log_mgr->Terminate(); thread_mgr->Terminate(); @@ -336,6 +340,7 @@ void terminate_bro() delete dpm; delete log_mgr; delete thread_mgr; + delete file_mgr; delete reporter; reporter = 0; @@ -775,7 +780,8 @@ int main(int argc, char** argv) remote_serializer = new RemoteSerializer(); event_registry = new EventRegistry(); log_mgr = new logging::Manager(); - input_mgr = new input::Manager(); + input_mgr = new input::Manager(); + file_mgr = new file_analysis::Manager(); if ( events_file ) event_player = new EventPlayer(events_file);