// See the file "COPYING" in the main distribution directory for copyright. #include "zeek/analyzer/Analyzer.h" #include #include #include "zeek/Conn.h" #include "zeek/Event.h" #include "zeek/analyzer/Manager.h" #include "zeek/packet_analysis/protocol/ip/conn_key/IPBasedConnKey.h" #include "zeek/packet_analysis/protocol/tcp/TCPSessionAdapter.h" #include "zeek/3rdparty/doctest.h" namespace zeek::analyzer { class AnalyzerTimer final : public zeek::detail::Timer { public: AnalyzerTimer(Analyzer* arg_analyzer, analyzer_timer_func arg_timer, double arg_t, int arg_do_expire, zeek::detail::TimerType arg_type); ~AnalyzerTimer() override; void Dispatch(double t, bool is_expire) override; protected: void Init(Analyzer* analyzer, analyzer_timer_func timer, int do_expire); Analyzer* analyzer = nullptr; analyzer_timer_func timer; int do_expire = 0; }; AnalyzerTimer::AnalyzerTimer(Analyzer* arg_analyzer, analyzer_timer_func arg_timer, double arg_t, int arg_do_expire, zeek::detail::TimerType arg_type) : Timer(arg_t, arg_type) { Init(arg_analyzer, arg_timer, arg_do_expire); } AnalyzerTimer::~AnalyzerTimer() { analyzer->RemoveTimer(this); Unref(analyzer->Conn()); } void AnalyzerTimer::Dispatch(double t, bool is_expire) { if ( is_expire && ! do_expire ) return; // Remove ourselves from the connection's set of timers so // it doesn't try to cancel us. analyzer->RemoveTimer(this); (analyzer->*timer)(t); } void AnalyzerTimer::Init(Analyzer* arg_analyzer, analyzer_timer_func arg_timer, int arg_do_expire) { analyzer = arg_analyzer; timer = arg_timer; do_expire = arg_do_expire; // We need to Ref the connection as the analyzer doesn't do it and // we need to have it around until we expire. Ref(analyzer->Conn()); } analyzer::ID Analyzer::id_counter = 0; const char* Analyzer::GetAnalyzerName() const { assert(tag); return analyzer_mgr->GetComponentName(tag).c_str(); } void Analyzer::SetAnalyzerTag(const zeek::Tag& arg_tag) { assert(! tag || tag == arg_tag); tag = arg_tag; } bool Analyzer::IsAnalyzer(const char* name) { assert(tag); return strcmp(analyzer_mgr->GetComponentName(tag).c_str(), name) == 0; } Analyzer::Analyzer(const char* name, Connection* conn) { zeek::Tag tag = analyzer_mgr->GetComponentTag(name); if ( ! tag ) reporter->InternalError("unknown analyzer name %s; mismatch with tag analyzer::Component?", name); CtorInit(tag, conn); } Analyzer::Analyzer(const zeek::Tag& tag, Connection* conn) { CtorInit(tag, conn); } Analyzer::Analyzer(Connection* conn) { CtorInit(zeek::Tag(), conn); } void Analyzer::CtorInit(const zeek::Tag& arg_tag, Connection* arg_conn) { // Don't Ref conn here to avoid circular ref'ing. It can't be deleted // before us. conn = arg_conn; tag = arg_tag; id = ++id_counter; protocol_confirmed = false; analyzer_confirmed = false; timers_canceled = false; skip = false; finished = false; removing = false; parent = nullptr; orig_supporters = nullptr; resp_supporters = nullptr; signature = nullptr; output_handler = nullptr; } Analyzer::~Analyzer() { assert(finished); assert(new_children.empty()); for ( Analyzer* a : children ) delete a; SupportAnalyzer* next = nullptr; for ( SupportAnalyzer* a = orig_supporters; a; a = next ) { next = a->sibling; delete a; } for ( SupportAnalyzer* a = resp_supporters; a; a = next ) { next = a->sibling; delete a; } delete output_handler; } void Analyzer::Init() {} void Analyzer::InitChildren() { AppendNewChildren(); for ( Analyzer* a : children ) { a->Init(); a->InitChildren(); } } void Analyzer::Done() { assert(! finished); if ( ! skip ) { EndOfData(true); EndOfData(false); } CancelTimers(); AppendNewChildren(); for ( Analyzer* a : children ) if ( ! a->finished ) a->Done(); for ( SupportAnalyzer* a = orig_supporters; a; a = a->sibling ) if ( ! a->finished ) a->Done(); for ( SupportAnalyzer* a = resp_supporters; a; a = a->sibling ) if ( ! a->finished ) a->Done(); finished = true; } void Analyzer::NextPacket(int len, const u_char* data, bool is_orig, uint64_t seq, const IP_Hdr* ip, int caplen) { if ( skip ) return; SupportAnalyzer* next_sibling = FirstSupportAnalyzer(is_orig); if ( next_sibling ) next_sibling->NextPacket(len, data, is_orig, seq, ip, caplen); else { try { DeliverPacket(len, data, is_orig, seq, ip, caplen); } catch ( binpac::Exception const& e ) { AnalyzerViolation(util::fmt("Binpac exception: %s", e.c_msg())); } } } void Analyzer::NextStream(int len, const u_char* data, bool is_orig) { if ( skip ) return; SupportAnalyzer* next_sibling = FirstSupportAnalyzer(is_orig); if ( next_sibling ) next_sibling->NextStream(len, data, is_orig); else { try { DeliverStream(len, data, is_orig); } catch ( binpac::Exception const& e ) { AnalyzerViolation(util::fmt("Binpac exception: %s", e.c_msg())); } } } void Analyzer::NextUndelivered(uint64_t seq, int len, bool is_orig) { if ( skip ) return; SupportAnalyzer* next_sibling = FirstSupportAnalyzer(is_orig); if ( next_sibling ) next_sibling->NextUndelivered(seq, len, is_orig); else { try { Undelivered(seq, len, is_orig); } catch ( binpac::Exception const& e ) { AnalyzerViolation(util::fmt("Binpac exception: %s", e.c_msg())); } } } void Analyzer::NextEndOfData(bool is_orig) { if ( skip ) return; SupportAnalyzer* next_sibling = FirstSupportAnalyzer(is_orig); if ( next_sibling ) next_sibling->NextEndOfData(is_orig); else EndOfData(is_orig); } void Analyzer::ForwardPacket(int len, const u_char* data, bool is_orig, uint64_t seq, const IP_Hdr* ip, int caplen) { if ( output_handler ) output_handler->DeliverPacket(len, data, is_orig, seq, ip, caplen); AppendNewChildren(); // Pass to all children. for ( auto i = children.begin(); i != children.end(); ) { Analyzer* current = *i; if ( ! (current->finished || current->removing) ) { current->NextPacket(len, data, is_orig, seq, ip, caplen); ++i; } else i = DeleteChild(i); } AppendNewChildren(); } void Analyzer::ForwardStream(int len, const u_char* data, bool is_orig) { if ( output_handler ) output_handler->DeliverStream(len, data, is_orig); AppendNewChildren(); for ( auto i = children.begin(); i != children.end(); ) { Analyzer* current = *i; if ( ! (current->finished || current->removing) ) { current->NextStream(len, data, is_orig); ++i; } else i = DeleteChild(i); } AppendNewChildren(); } void Analyzer::ForwardUndelivered(uint64_t seq, int len, bool is_orig) { if ( output_handler ) output_handler->Undelivered(seq, len, is_orig); AppendNewChildren(); for ( auto i = children.begin(); i != children.end(); ) { Analyzer* current = *i; if ( ! (current->finished || current->removing) ) { current->NextUndelivered(seq, len, is_orig); ++i; } else i = DeleteChild(i); } AppendNewChildren(); } void Analyzer::ForwardEndOfData(bool orig) { AppendNewChildren(); for ( auto i = children.begin(); i != children.end(); ) { Analyzer* current = *i; if ( ! (current->finished || current->removing) ) { current->NextEndOfData(orig); ++i; } else i = DeleteChild(i); } AppendNewChildren(); } bool Analyzer::AddChildAnalyzer(Analyzer* analyzer, bool init) { auto t = analyzer->GetAnalyzerTag(); // Prevent attaching child analyzers to analyzer subtrees where // either the parent has finished or is being removed. Further, // don't attach analyzers when the connection has finished or is // currently being finished (executing Done()). // // Scenarios in which analyzers have been observed that late in // analyzer / connection lifetime are: // // * A DPD signature match on undelivered TCP data that is flushed // during Connection::Done(). The PIA analyzer activates a new // analyzer adding it to the TCP analyzer. // // * Analyzers flushing buffered state during Done(), resulting // in new analyzers being created. // // Analyzers added during Done() are problematic as calling Done() // within the parent's destructor isn't safe, so we prevent these // situations. if ( Removing() || IsFinished() || Conn()->IsFinished() ) { analyzer->Done(); delete analyzer; return false; } if ( HasChildAnalyzer(t) || IsPreventedChildAnalyzer(t) ) { analyzer->Done(); delete analyzer; return false; } // We add new children to new_children first. They are then // later copied to the "real" child list. This is necessary // because this method may be called while somebody is iterating // over the children and we might confuse the caller by modifying // the list. analyzer->parent = this; new_children.push_back(analyzer); if ( init ) analyzer->Init(); DBG_LOG(DBG_ANALYZER, "%s added child %s", fmt_analyzer(this).c_str(), fmt_analyzer(analyzer).c_str()); return true; } Analyzer* Analyzer::AddChildAnalyzer(const zeek::Tag& analyzer) { if ( HasChildAnalyzer(analyzer) ) return nullptr; if ( IsPreventedChildAnalyzer(tag) ) return nullptr; Analyzer* a = analyzer_mgr->InstantiateAnalyzer(analyzer, conn); if ( a && AddChildAnalyzer(a) ) return a; return nullptr; } bool Analyzer::RemoveChild(const analyzer_list& children, ID id) { for ( const auto& i : children ) { if ( i->id != id ) continue; if ( i->finished || i->removing ) return false; DBG_LOG(DBG_ANALYZER, "%s disabling child %s", fmt_analyzer(this).c_str(), fmt_analyzer(i).c_str()); // We just flag it as being removed here but postpone // actually doing that to later. Otherwise, we'd need // to call Done() here, which then in turn might // cause further code to be executed that may assume // something not true because of a violation that // triggered the removal in the first place. i->removing = true; return true; } return false; } bool Analyzer::RemoveChildAnalyzer(ID id) { return RemoveChild(children, id) || RemoveChild(new_children, id); } bool Analyzer::Remove() { assert(parent); parent->RemoveChildAnalyzer(this); return removing; } void Analyzer::PreventChildren(const zeek::Tag& tag) { if ( IsPreventedChildAnalyzer(tag) ) return; prevented.emplace_back(tag); } bool Analyzer::IsPreventedChildAnalyzer(const zeek::Tag& tag) const { return std::find(prevented.begin(), prevented.end(), tag) != prevented.end(); } bool Analyzer::HasChildAnalyzer(const zeek::Tag& tag) const { return GetChildAnalyzer(tag) != nullptr; } Analyzer* Analyzer::GetChildAnalyzer(const zeek::Tag& tag) const { for ( Analyzer* a : children ) if ( a->tag == tag && ! (a->removing || a->finished) ) return a; for ( Analyzer* a : new_children ) if ( a->tag == tag && ! (a->removing || a->finished) ) return a; return nullptr; } Analyzer* Analyzer::GetChildAnalyzer(const std::string& name) const { for ( Analyzer* a : children ) if ( a->GetAnalyzerName() == name && ! (a->removing || a->finished) ) return a; for ( Analyzer* a : new_children ) if ( a->GetAnalyzerName() == name && ! (a->removing || a->finished) ) return a; return nullptr; } Analyzer* Analyzer::FindChild(ID arg_id) { if ( id == arg_id && ! (removing || finished) ) return this; for ( Analyzer* a : children ) { if ( Analyzer* child = a->FindChild(arg_id) ) return child; } for ( Analyzer* a : new_children ) { if ( Analyzer* child = a->FindChild(arg_id) ) return child; } return nullptr; } Analyzer* Analyzer::FindChild(zeek::Tag arg_tag) { if ( tag == arg_tag && ! (removing || finished) ) return this; for ( Analyzer* a : children ) { if ( Analyzer* child = a->FindChild(arg_tag) ) return child; } for ( Analyzer* a : new_children ) { if ( Analyzer* child = a->FindChild(arg_tag) ) return child; } return nullptr; } Analyzer* Analyzer::FindChild(const char* name) { zeek::Tag tag = analyzer_mgr->GetComponentTag(name); return tag ? FindChild(tag) : nullptr; } void Analyzer::CleanupChildren() { AppendNewChildren(); for ( auto i = children.begin(); i != children.end(); ) { if ( ! ((*i)->finished || (*i)->removing) ) ++i; else i = DeleteChild(i); } } analyzer_list::iterator Analyzer::DeleteChild(analyzer_list::iterator i) { Analyzer* child = *i; // Analyzer must have already been finished or marked for removal. assert(child->finished || child->removing); if ( child->removing ) { child->Done(); child->removing = false; } DBG_LOG(DBG_ANALYZER, "%s deleted child %s 3", fmt_analyzer(this).c_str(), fmt_analyzer(child).c_str()); auto next = children.erase(i); delete child; return next; } void Analyzer::AddSupportAnalyzer(SupportAnalyzer* analyzer) { if ( HasSupportAnalyzer(analyzer->GetAnalyzerTag(), analyzer->IsOrig()) ) { DBG_LOG(DBG_ANALYZER, "%s already has %s %s", fmt_analyzer(this).c_str(), analyzer->IsOrig() ? "originator" : "responder", fmt_analyzer(analyzer).c_str()); analyzer->Done(); delete analyzer; return; } SupportAnalyzer** head = analyzer->IsOrig() ? &orig_supporters : &resp_supporters; // Find end of the list. SupportAnalyzer* prev = nullptr; SupportAnalyzer* s; for ( s = *head; s; prev = s, s = s->sibling ) ; if ( prev ) prev->sibling = analyzer; else *head = analyzer; analyzer->parent = this; analyzer->Init(); DBG_LOG(DBG_ANALYZER, "%s added %s support %s", fmt_analyzer(this).c_str(), analyzer->IsOrig() ? "originator" : "responder", fmt_analyzer(analyzer).c_str()); } void Analyzer::RemoveSupportAnalyzer(SupportAnalyzer* analyzer) { DBG_LOG(DBG_ANALYZER, "%s disabled %s support analyzer %s", fmt_analyzer(this).c_str(), analyzer->IsOrig() ? "originator" : "responder", fmt_analyzer(analyzer).c_str()); // We mark the analyzer as being removed here, which will prevent it // from being used further. However, we don't actually delete it // before the parent gets destroyed. While we could do that, it's a // bit tricky to do at the right time and it doesn't seem worth the // trouble. analyzer->removing = true; return; } bool Analyzer::HasSupportAnalyzer(const zeek::Tag& tag, bool orig) { SupportAnalyzer* s = orig ? orig_supporters : resp_supporters; for ( ; s; s = s->sibling ) if ( s->tag == tag ) return true; return false; } SupportAnalyzer* Analyzer::FirstSupportAnalyzer(bool orig) { SupportAnalyzer* sa = orig ? orig_supporters : resp_supporters; if ( ! sa ) return nullptr; if ( ! sa->Removing() ) return sa; return sa->Sibling(true); } void Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig, uint64_t seq, const IP_Hdr* ip, int caplen) { DBG_LOG(DBG_ANALYZER, "%s DeliverPacket(%d, %s, %" PRIu64 ", %p, %d) [%s%s]", fmt_analyzer(this).c_str(), len, is_orig ? "T" : "F", seq, ip, caplen, util::fmt_bytes((const char*)data, min(40, len)), len > 40 ? "..." : ""); } void Analyzer::DeliverStream(int len, const u_char* data, bool is_orig) { DBG_LOG(DBG_ANALYZER, "%s DeliverStream(%d, %s) [%s%s]", fmt_analyzer(this).c_str(), len, is_orig ? "T" : "F", util::fmt_bytes((const char*)data, min(40, len)), len > 40 ? "..." : ""); } void Analyzer::Undelivered(uint64_t seq, int len, bool is_orig) { DBG_LOG(DBG_ANALYZER, "%s Undelivered(%" PRIu64 ", %d, %s)", fmt_analyzer(this).c_str(), seq, len, is_orig ? "T" : "F"); } void Analyzer::EndOfData(bool is_orig) { DBG_LOG(DBG_ANALYZER, "%s EndOfData(%s)", fmt_analyzer(this).c_str(), is_orig ? "T" : "F"); } void Analyzer::FlipRoles() { DBG_LOG(DBG_ANALYZER, "%s FlipRoles()", fmt_analyzer(this).c_str()); for ( Analyzer* a : children ) a->FlipRoles(); for ( Analyzer* a : new_children ) a->FlipRoles(); for ( SupportAnalyzer* a = orig_supporters; a; a = a->sibling ) a->FlipRoles(); for ( SupportAnalyzer* a = resp_supporters; a; a = a->sibling ) a->FlipRoles(); SupportAnalyzer* tmp = orig_supporters; orig_supporters = resp_supporters; resp_supporters = tmp; } void Analyzer::EnqueueAnalyzerConfirmationInfo(const zeek::Tag& arg_tag) { static auto info_type = zeek::id::find_type("AnalyzerConfirmationInfo"); static auto info_c_idx = info_type->FieldOffset("c"); static auto info_aid_idx = info_type->FieldOffset("aid"); auto info = make_intrusive(info_type); info->Assign(info_c_idx, ConnVal()); info->Assign(info_aid_idx, val_mgr->Count(id)); event_mgr.Enqueue(analyzer_confirmation_info, arg_tag.AsVal(), info); } void Analyzer::AnalyzerConfirmation(zeek::Tag arg_tag) { if ( analyzer_confirmed ) return; analyzer_confirmed = true; const auto& effective_tag = arg_tag ? arg_tag : tag; if ( analyzer_confirmation_info ) EnqueueAnalyzerConfirmationInfo(effective_tag); } void Analyzer::EnqueueAnalyzerViolationInfo(const char* reason, const char* data, int len, const zeek::Tag& arg_tag) { static auto info_type = zeek::id::find_type("AnalyzerViolationInfo"); static auto info_reason_idx = info_type->FieldOffset("reason"); static auto info_c_idx = info_type->FieldOffset("c"); static auto info_aid_idx = info_type->FieldOffset("aid"); static auto info_data_idx = info_type->FieldOffset("data"); auto info = zeek::make_intrusive(info_type); info->Assign(info_reason_idx, make_intrusive(reason)); info->Assign(info_c_idx, ConnVal()); info->Assign(info_aid_idx, val_mgr->Count(id)); if ( data && len ) info->Assign(info_data_idx, make_intrusive(len, data)); event_mgr.Enqueue(analyzer_violation_info, arg_tag.AsVal(), info); } void Analyzer::AnalyzerViolation(const char* reason, const char* data, int len, zeek::Tag arg_tag) { const auto& effective_tag = arg_tag ? arg_tag : tag; ++analyzer_violations; if ( analyzer_violations > BifConst::max_analyzer_violations ) { if ( analyzer_violations == BifConst::max_analyzer_violations + 1 ) Weird("too_many_analyzer_violations"); return; } if ( analyzer_violation_info ) EnqueueAnalyzerViolationInfo(reason, data, len, effective_tag); } void Analyzer::AddTimer(analyzer_timer_func timer, double t, bool do_expire, zeek::detail::TimerType type) { zeek::detail::Timer* analyzer_timer = new AnalyzerTimer(this, timer, t, do_expire, type); zeek::detail::timer_mgr->Add(analyzer_timer); timers.push_back(analyzer_timer); } void Analyzer::RemoveTimer(zeek::detail::Timer* t) { timers.remove(t); } void Analyzer::CancelTimers() { // We are going to cancel our timers which, in turn, may cause them to // call RemoveTimer(), which would then modify the list we're just // traversing. Thus, we first make a copy of the list which we then // iterate through. TimerPList tmp(timers.length()); std::copy(timers.begin(), timers.end(), back_inserter(tmp)); // TODO: could be a for_each for ( auto timer : tmp ) zeek::detail::timer_mgr->Cancel(timer); timers_canceled = true; timers.clear(); } void Analyzer::AppendNewChildren() { for ( Analyzer* a : new_children ) children.push_back(a); new_children.clear(); } void Analyzer::UpdateConnVal(RecordVal* conn_val) { for ( Analyzer* a : children ) a->UpdateConnVal(conn_val); } const RecordValPtr& Analyzer::ConnVal() { return conn->GetVal(); } void Analyzer::Event(EventHandlerPtr f, const char* name) { conn->Event(f, this, name); } void Analyzer::EnqueueConnEvent(EventHandlerPtr f, Args args) { conn->EnqueueEvent(f, this, std::move(args)); } void Analyzer::Weird(const char* name, const char* addl) { conn->Weird(name, addl, GetAnalyzerName()); } SupportAnalyzer* SupportAnalyzer::Sibling(bool only_active) const { if ( ! only_active ) return sibling; SupportAnalyzer* next = sibling; while ( next && next->Removing() ) next = next->sibling; return next; } void SupportAnalyzer::ForwardPacket(int len, const u_char* data, bool is_orig, uint64_t seq, const IP_Hdr* ip, int caplen) { // We do not call parent's method, as we're replacing the functionality. if ( GetOutputHandler() ) { GetOutputHandler()->DeliverPacket(len, data, is_orig, seq, ip, caplen); return; } // If the parent is being removed or has finished, there's little point // for a support analyzers to move packets forward. if ( Parent()->Removing() || Parent()->IsFinished() ) return; SupportAnalyzer* next_sibling = Sibling(true); if ( next_sibling ) // Pass to next in chain. next_sibling->NextPacket(len, data, is_orig, seq, ip, caplen); else // Finished with preprocessing - now it's the parent's turn. Parent()->DeliverPacket(len, data, is_orig, seq, ip, caplen); } void SupportAnalyzer::ForwardStream(int len, const u_char* data, bool is_orig) { // We do not call parent's method, as we're replacing the functionality. if ( GetOutputHandler() ) { GetOutputHandler()->DeliverStream(len, data, is_orig); return; } // If the parent is being removed or has finished, there's little point // for a support analyzers to move stream data forward. if ( Parent()->Removing() || Parent()->IsFinished() ) return; SupportAnalyzer* next_sibling = Sibling(true); if ( next_sibling ) // Pass to next in chain. next_sibling->NextStream(len, data, is_orig); else // Finished with preprocessing - now it's the parent's turn. Parent()->DeliverStream(len, data, is_orig); } void SupportAnalyzer::ForwardUndelivered(uint64_t seq, int len, bool is_orig) { // We do not call parent's method, as we're replacing the functionality. if ( GetOutputHandler() ) { GetOutputHandler()->Undelivered(seq, len, is_orig); return; } SupportAnalyzer* next_sibling = Sibling(true); if ( next_sibling ) // Pass to next in chain. next_sibling->NextUndelivered(seq, len, is_orig); else // Finished with preprocessing - now it's the parent's turn. Parent()->Undelivered(seq, len, is_orig); } } // namespace zeek::analyzer TEST_SUITE("Analyzer management") { TEST_CASE("Re-add analyzer after removal") { // This test tries to reactivate an analyzer which was previously removed. // It's a regression test for #2801. REQUIRE(zeek::analyzer_mgr); zeek::Packet p; zeek::IPBasedConnKeyPtr kp = std::make_unique(); auto conn = std::make_unique(std::move(kp), 0, 0, &p); auto* tcp = new zeek::packet_analysis::TCP::TCPSessionAdapter(conn.get()); conn->SetSessionAdapter(tcp, nullptr); auto a = zeek::analyzer_mgr->InstantiateAnalyzer("SSH", conn.get()); REQUIRE(a); auto b1 = zeek::analyzer_mgr->InstantiateAnalyzer("IMAP", a->Conn()); REQUIRE(b1); CHECK(tcp->AddChildAnalyzer(a)); CHECK(a->AddChildAnalyzer(b1)); CHECK(conn->FindAnalyzer("SSH")); CHECK(conn->FindAnalyzer("IMAP")); CHECK(a->RemoveChildAnalyzer(b1)); CHECK(! conn->FindAnalyzer("IMAP")); auto b2 = zeek::analyzer_mgr->InstantiateAnalyzer("IMAP", a->Conn()); REQUIRE(b2); REQUIRE(a->AddChildAnalyzer(b2)); CHECK(conn->FindAnalyzer("IMAP")); conn->Done(); } TEST_CASE("Analyzer mapping") { REQUIRE(zeek::analyzer_mgr); zeek::Packet p; zeek::IPBasedConnKeyPtr kp = std::make_unique(); auto conn = std::make_unique(std::move(kp), 0, 0, &p); auto ssh = zeek::analyzer_mgr->InstantiateAnalyzer("SSH", conn.get()); REQUIRE(ssh); auto imap = zeek::analyzer_mgr->InstantiateAnalyzer("IMAP", conn.get()); REQUIRE(imap); zeek::analyzer_mgr->AddComponentMapping(ssh->GetAnalyzerTag(), imap->GetAnalyzerTag()); zeek::analyzer_mgr->DisableAnalyzer(ssh->GetAnalyzerTag()); // needs to be disabled for mapping to take effect auto ssh_is_imap = zeek::analyzer_mgr->InstantiateAnalyzer("SSH", conn.get()); CHECK_EQ(ssh_is_imap->GetAnalyzerTag(), imap->GetAnalyzerTag()); // SSH is now IMAP // orderly cleanup through connection auto* tcp = new zeek::packet_analysis::TCP::TCPSessionAdapter(conn.get()); conn->SetSessionAdapter(tcp, nullptr); tcp->AddChildAnalyzer(ssh); tcp->AddChildAnalyzer(imap); tcp->AddChildAnalyzer(ssh_is_imap); conn->Done(); } }