]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #3189: Roll AppId's SSH detector into SSH service inspector
authorShravan Rangarajuvenkata (shrarang) <shrarang@cisco.com>
Tue, 14 Dec 2021 20:38:57 +0000 (20:38 +0000)
committerShravan Rangarajuvenkata (shrarang) <shrarang@cisco.com>
Tue, 14 Dec 2021 20:38:57 +0000 (20:38 +0000)
Merge in SNORT/snort3 from ~SHRARANG/snort3:appid_ssh to master

Squashed commit of the following:

commit 49d2ca8ea4b6b75607dc2169a41d0efff2490354
Author: Shravan Rangaraju <shrarang@cisco.com>
Date:   Tue Nov 30 23:11:23 2021 -0500

    framework, appid: generate NO_SERVICE event when no inspector can be attached to a flow; wait for the event in appid before declaring service as unknown for the flow

commit 7cfa805c36bae248f12dde37a4cdc073bd24a797
Author: Shravan Rangaraju <shrarang@cisco.com>
Date:   Tue Nov 30 17:14:55 2021 -0500

    appid: remove hard-coded SSH client patterns which are available as part of ODP

commit a9cdcc3457b03bfa5f37e5bd2c6ae252c11fe247
Author: Shravan Rangaraju <shrarang@cisco.com>
Date:   Tue Nov 30 14:59:27 2021 -0500

    appid, ssh: Roll AppId's SSH detector into SSH service inspector

26 files changed:
src/flow/flow.h
src/framework/data_bus.h
src/managers/inspector_manager.cc
src/network_inspectors/appid/CMakeLists.txt
src/network_inspectors/appid/appid_config.cc
src/network_inspectors/appid/appid_discovery.cc
src/network_inspectors/appid/appid_inspector.cc
src/network_inspectors/appid/appid_service_event_handler.cc [new file with mode: 0644]
src/network_inspectors/appid/appid_service_event_handler.h [moved from src/network_inspectors/appid/service_plugins/service_ssh.h with 61% similarity]
src/network_inspectors/appid/appid_session.h
src/network_inspectors/appid/appid_ssh_event_handler.cc [new file with mode: 0644]
src/network_inspectors/appid/appid_ssh_event_handler.h [moved from src/network_inspectors/appid/client_plugins/client_app_ssh.h with 56% similarity]
src/network_inspectors/appid/client_plugins/client_app_ssh.cc [deleted file]
src/network_inspectors/appid/client_plugins/client_discovery.cc
src/network_inspectors/appid/detector_plugins/ssh_patterns.cc
src/network_inspectors/appid/detector_plugins/ssh_patterns.h
src/network_inspectors/appid/service_plugins/service_discovery.cc
src/network_inspectors/appid/service_plugins/service_ssh.cc [deleted file]
src/network_inspectors/binder/binder.cc
src/pub_sub/CMakeLists.txt
src/pub_sub/ssh_events.h [new file with mode: 0644]
src/service_inspectors/ssh/ssh.cc
src/service_inspectors/ssh/ssh.h
src/service_inspectors/ssh/ssh_splitter.cc
src/service_inspectors/ssh/ssh_splitter.h
src/service_inspectors/wizard/wizard.cc

index d3103c60362b060f9c42de6b9cf0c40c69ff39da..276485c42a364f1310d28a083fa740a9e2e8b418 100644 (file)
@@ -484,6 +484,7 @@ public:  // FIXIT-M privatize if possible
         bool data_decrypted : 1;    // indicate data in current flow is decrypted TLS application data
         bool snort_proto_id_set_by_ha : 1;
         bool efd_flow : 1;  // Indicate that current flow is an elephant flow
+        bool svc_event_generated : 1; // Set if FLOW_NO_SERVICE_EVENT was generated for this flow
     } flags;
 
     FlowState flow_state;
index 1707fa69a42728a3a7bf94ce15f194875a893519..cd18361321c8861790bf3632a6501cc4e447642d 100644 (file)
@@ -139,6 +139,8 @@ private:
 #define FLOW_SERVICE_CHANGE_EVENT "flow.service_change_event"
 // A flow has found the service inspector
 #define SERVICE_INSPECTOR_CHANGE_EVENT "flow.service_inspector.changed"
+// No service has been found for the flow
+#define FLOW_NO_SERVICE_EVENT "flow.no_service_event"
 // search of SSL is abandoned on this flow
 #define SSL_SEARCH_ABANDONED "flow.ssl_search_abandoned"
 
index 009cd538b51765f6a6c0531b48bf1265501b2f35..b18a0c998224fb3d7900ba4fe2cc4d4a07b9f932 100644 (file)
@@ -1464,7 +1464,18 @@ void InspectorManager::bumble(Packet* p)
 
     flow->clear_clouseau();
 
-    if ( !flow->gadget || !flow->is_stream() )
+    if ( !flow->gadget )
+    {
+        if ( !flow->flags.svc_event_generated )
+        {
+            DataBus::publish(FLOW_NO_SERVICE_EVENT, p);
+            flow->flags.svc_event_generated = true;
+        }
+
+        return;
+    }
+
+    if ( !flow->is_stream() )
         return;
 
     if ( flow->session )
index fac670bb47367e6d794ccbc7a6e3755dff890e59..2b10e96e548aa1862e8ef671f5625e4606f3d756 100644 (file)
@@ -25,8 +25,6 @@ set ( CP_APPID_SOURCES
     client_plugins/client_app_msn.h
     client_plugins/client_app_rtp.cc
     client_plugins/client_app_rtp.h
-    client_plugins/client_app_ssh.cc
-    client_plugins/client_app_ssh.h
     client_plugins/client_app_timbuktu.cc
     client_plugins/client_app_timbuktu.h
     client_plugins/client_app_tns.cc
@@ -92,8 +90,6 @@ set ( SP_APPID_SOURCES
     service_plugins/service_rtmp.h
     service_plugins/service_snmp.cc
     service_plugins/service_snmp.h
-    service_plugins/service_ssh.cc
-    service_plugins/service_ssh.h
     service_plugins/service_ssl.cc
     service_plugins/service_ssl.h
     service_plugins/service_telnet.cc
@@ -164,6 +160,8 @@ set ( APPID_SOURCES
     appid_opportunistic_tls_event_handler.h
     appid_peg_counts.h
     appid_peg_counts.cc
+    appid_service_event_handler.cc
+    appid_service_event_handler.h
     appid_session.cc
     appid_session.h
     appid_session_api.cc
@@ -174,6 +172,8 @@ set ( APPID_SOURCES
     appid_module.cc
     appid_module.h
     appid_pegs.h
+    appid_ssh_event_handler.cc
+    appid_ssh_event_handler.h
     appid_stats.cc
     appid_stats.h
     app_info_table.cc
index 3d07f290e005c4a2092479a0325f869a8dfc0a1d..9db516646e2868d6be720ab6ceb8bc0ee020ac89 100644 (file)
@@ -173,7 +173,6 @@ void OdpContext::initialize(AppIdInspector& inspector)
     sip_matchers.finalize_patterns(*this);
     ssl_matchers.finalize_patterns();
     dns_matchers.finalize_patterns();
-    ssh_matchers.finalize_patterns();
 }
 
 void OdpContext::reload()
index 110e99a3df015b6d8a07840b41d12338e126923a..0df7d941e7c1856bc557dccc076a4e4d329bee73 100644 (file)
@@ -248,6 +248,14 @@ bool AppIdDiscovery::do_pre_discovery(Packet* p, AppIdSession*& asd, AppIdInspec
         else if (appidDebug->is_active())
             LogMessage("AppIdDbg %s New AppId session\n", appidDebug->get_debug_session());
     }
+    else if (!asd->get_session_flags(APPID_SESSION_MID) and
+        (p->flow->get_session_flags() & SSNFLAG_MIDSTREAM))
+    {
+        asd->flags |= APPID_SESSION_MID;
+        if (appidDebug->is_active())
+            LogMessage("AppIdDbg %s AppId mid-stream session\n", appidDebug->get_debug_session());
+    }
+
     if (!asd->get_session_flags(APPID_SESSION_DISCOVER_APP | APPID_SESSION_SPECIAL_MONITORED))
         return false;
 
index 28bcfa6d3ad83c13b92b4ccfcf9e2c1eb6dec6e9..9d9bf96c1722a8af429b874c120b0145d0ed82d1 100644 (file)
@@ -44,7 +44,9 @@
 #include "appid_http2_req_body_event_handler.h"
 #include "appid_opportunistic_tls_event_handler.h"
 #include "appid_peg_counts.h"
+#include "appid_service_event_handler.h"
 #include "appid_session.h"
+#include "appid_ssh_event_handler.h"
 #include "appid_stats.h"
 #include "client_plugins/client_discovery.h"
 #include "detector_plugins/detector_pattern.h"
@@ -138,6 +140,10 @@ bool AppIdInspector::configure(SnortConfig* sc)
 
     DataBus::subscribe_network(EFP_PROCESS_EVENT, new AppIdEfpProcessEventHandler());
 
+    DataBus::subscribe_network(SSH_EVENT, new SshEventHandler());
+
+    DataBus::subscribe_network(FLOW_NO_SERVICE_EVENT, new AppIdServiceEventHandler(*this));
+
     return true;
 }
 
diff --git a/src/network_inspectors/appid/appid_service_event_handler.cc b/src/network_inspectors/appid/appid_service_event_handler.cc
new file mode 100644 (file)
index 0000000..6be33be
--- /dev/null
@@ -0,0 +1,122 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// appid_service_event_handler.cc author Shravan Rangaraju <shrarang@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "appid_service_event_handler.h"
+
+#include "detection/detection_engine.h"
+#include "protocols/packet.h"
+#include "appid_api.h"
+#include "appid_debug.h"
+#include "appid_inspector.h"
+#include "appid_session.h"
+
+using namespace snort;
+
+void AppIdServiceEventHandler::handle(DataEvent&, Flow* flow)
+{
+    if (!pkt_thread_odp_ctxt or !flow)
+        return;
+
+    Packet* p = DetectionEngine::get_current_packet();
+    assert(p);
+
+    // FIXIT-E: For now, wait for snort service inspection only for TCP. In the future, if AppId
+    // rolls any of its UDP detectors into service inspectors, below check needs to be removed.
+    if (!p->is_tcp())
+        return;
+
+    AppIdSession* asd = appid_api.get_appid_session(*flow);
+    AppidSessionDirection dir;
+
+    if (asd and asd->initiator_port)
+        dir = (asd->initiator_port == p->ptrs.sp) ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER;
+    else
+        dir = p->is_from_client() ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER;
+
+    if (!asd)
+    {
+        // Event is received before appid has seen any packet. Example, UDP in inline mode
+        asd = AppIdSession::allocate_session(p, p->get_ip_proto_next(), dir,
+            inspector, *pkt_thread_odp_ctxt);
+        if (appidDebug->is_enabled())
+        {
+            appidDebug->activate(flow, asd, inspector.get_ctxt().config.log_all_sessions);
+            if (appidDebug->is_active())
+                LogMessage("AppIdDbg %s New AppId session at service event\n",
+                    appidDebug->get_debug_session());
+        }
+    }
+    else if (asd->get_odp_ctxt_version() != pkt_thread_odp_ctxt->get_version())
+        return; // Skip detection for sessions using old odp context after odp reload
+    if (!asd->get_session_flags(APPID_SESSION_DISCOVER_APP | APPID_SESSION_SPECIAL_MONITORED))
+        return;
+
+    asd->set_no_service_inspector();
+
+    if (!asd->has_no_service_candidate())
+    {
+        if (appidDebug->is_active())
+            LogMessage("AppIdDbg %s No service inspector\n",
+                appidDebug->get_debug_session());
+        return;
+    }
+
+    if (appidDebug->is_active())
+        LogMessage("AppIdDbg %s No service candidate and no inspector\n", appidDebug->get_debug_session());
+
+    const SfIp* service_ip;
+    uint16_t port;
+    int16_t group;
+    auto proto = asd->protocol;
+
+    if (asd->is_service_ip_set())
+        std::tie(service_ip, port, group) = asd->get_server_info();
+    else
+    {
+        if (dir == APP_ID_FROM_RESPONDER)
+        {
+            service_ip = p->ptrs.ip_api.get_src();
+            port = p->ptrs.sp;
+            group = p->get_ingress_group();
+        }
+        else
+        {
+            service_ip = p->ptrs.ip_api.get_dst();
+            port = p->ptrs.dp;
+            group = p->get_egress_group();
+        }
+        asd->set_server_info(*service_ip, port, group);
+    }
+
+    const SfIp* client_ip;
+    if (dir == APP_ID_FROM_RESPONDER)
+        client_ip = p->ptrs.ip_api.get_dst();
+    else
+        client_ip = p->ptrs.ip_api.get_src();
+
+    ServiceDiscoveryState* sds = AppIdServiceState::add(service_ip, proto, port, group, asd->asid,
+        asd->is_decrypted(), true);
+    asd->get_odp_ctxt().get_service_disco_mgr().fail_service(*asd, p, dir, nullptr, sds);
+    sds->set_service_id_failed(*asd, client_ip);
+}
similarity index 61%
rename from src/network_inspectors/appid/service_plugins/service_ssh.h
rename to src/network_inspectors/appid/appid_service_event_handler.h
index f977bb6853a19e72bf0ed134e71621ad4e6ad4a4..a8ae433eb6c6a4bbca4d53345960f28e66c698d5 100644 (file)
@@ -1,6 +1,5 @@
 //--------------------------------------------------------------------------
-// Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
-// Copyright (C) 2005-2013 Sourcefire, Inc.
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
 //
 // This program is free software; you can redistribute it and/or modify it
 // under the terms of the GNU General Public License Version 2 as published
 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //--------------------------------------------------------------------------
 
-// service_ssh.h author Sourcefire Inc.
+// appid_service_event_handler.h author Shravan Rangaraju <shrarang@cisco.com>
 
-#ifndef SERVICE_SSH_H
-#define SERVICE_SSH_H
+#ifndef APPID_SERVICE_EVENT_HANDLER_H
+#define APPID_SERVICE_EVENT_HANDLER_H
 
-#include "service_detector.h"
+#include "framework/data_bus.h"
 
-class ServiceDiscovery;
+#include "appid_module.h"
 
-class SshServiceDetector : public ServiceDetector
+namespace snort
+{
+class Flow;
+}
+
+class AppIdServiceEventHandler : public snort::DataHandler
 {
 public:
-    SshServiceDetector(ServiceDiscovery*);
+    AppIdServiceEventHandler(AppIdInspector& inspector) :
+        DataHandler(MOD_NAME), inspector(inspector)
+    { }
+
+    void handle(snort::DataEvent&, snort::Flow* flow) override;
 
-    int validate(AppIdDiscoveryArgs&) override;
+private:
+    AppIdInspector& inspector;
 };
-#endif
 
+#endif
index a0494e8fcf53ec1f53465e3cc9eb4e853730103b..3f1da2acbdeaecffb97a7bb6b18e87df36d65a83 100644 (file)
@@ -623,6 +623,26 @@ public:
         consumed_ha_data = val;
     }
 
+    bool has_no_service_candidate() const
+    {
+        return no_service_candidate;
+    }
+
+    void set_no_service_candidate()
+    {
+        no_service_candidate = true;
+    }
+
+    bool has_no_service_inspector() const
+    {
+        return no_service_inspector;
+    }
+
+    void set_no_service_inspector()
+    {
+        no_service_inspector = true;
+    }
+
 private:
     uint16_t prev_http2_raw_packet = 0;
 
@@ -643,6 +663,8 @@ private:
     uint32_t odp_ctxt_version;
     ThirdPartyAppIdContext* tp_appid_ctxt = nullptr;
     bool consumed_ha_data = false;
+    bool no_service_candidate = false;
+    bool no_service_inspector = false;
 };
 
 #endif
diff --git a/src/network_inspectors/appid/appid_ssh_event_handler.cc b/src/network_inspectors/appid/appid_ssh_event_handler.cc
new file mode 100644 (file)
index 0000000..fe994f7
--- /dev/null
@@ -0,0 +1,246 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// appid_ssh_event_handler.cc author Daniel McGarvey <danmcgar@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "appid_ssh_event_handler.h"
+
+#include "detector_plugins/ssh_patterns.h"
+#include "service_inspectors/ssh/ssh.h"
+#include "appid_debug.h"
+#include "appid_detector.h"
+#include "appid_inspector.h"
+
+using namespace snort;
+using namespace std;
+
+static bool handle_protocol(SshEvent& event, SshAppIdInfo* fd)
+{
+    // FIXIT-L
+    // There isn't any real specification on what separates the vendor name from version number. It
+    // seems to usually be an underscore or dash, but there are some clients where this pattern
+    // isn't followed. For example, in the Bitvise SSH client, the version string has the version
+    // number first, separated by a space from the subsequent client identification. Parsing may
+    // need to be enhanced to handle unusual version strings for new client apps.
+    const string& protocol = event.get_version_str();
+
+    const char* vendor_begin = strchr(protocol.c_str() + sizeof(SSH_BANNER) - 1, '-');
+    if (vendor_begin != nullptr)
+        vendor_begin++;
+    else
+        return false;
+
+    const char* vendor_end = strpbrk(vendor_begin, "_- \r\n");
+    if (vendor_end == nullptr)
+        return false;
+
+    size_t vendor_len = (size_t)(vendor_end - vendor_begin);
+    fd->vendor.assign(vendor_begin, vendor_len);
+
+    const char* version_begin = vendor_end + 1;
+    const char* version_end  = strpbrk(version_begin, " \r\n");
+    if (version_end == nullptr)
+        return false;
+    size_t version_len = (size_t)(version_end - version_begin);
+    fd->version.assign(version_begin, version_len);
+
+    if (appidDebug->is_active())
+        LogMessage("AppIdDbg %s SSH event handler read SSH version string with vendor %s and version %s\n",
+            appidDebug->get_debug_session(), fd->vendor.c_str(), fd->version.c_str());
+
+    return true;
+}
+
+
+static void handle_failure(AppIdSession& asd, SshEventFlowData& data)
+{
+    asd.set_service_id(APP_ID_UNKNOWN, asd.get_odp_ctxt());
+    asd.set_service_detected();
+
+    asd.set_client_id(APP_ID_UNKNOWN);
+    asd.set_client_detected();
+
+    data.failed = true;
+}
+
+static void client_success(const SshAppIdInfo& fd, AppIdSession& asd, AppidChangeBits& change_bits)
+{
+    SshPatternMatchers& table = asd.get_odp_ctxt().get_ssh_matchers();
+    AppId client_id;
+
+    if (table.has_pattern(fd.vendor))
+    {
+        client_id = table.get_appid(fd.vendor);
+        if (appidDebug->is_active())
+            LogMessage("AppIdDbg %s SSH event handler identified client with AppId %u\n",
+                appidDebug->get_debug_session(), client_id);
+    }
+    else
+    {
+        client_id = APP_ID_SSH;
+        if (appidDebug->is_active())
+            LogMessage("AppIdDbg %s SSH event handler client detected, but vendor not recognized\n",
+                appidDebug->get_debug_session());
+    }
+
+    asd.set_client_id(client_id);
+    asd.set_ss_application_ids(client_id, APP_ID_NONE, change_bits);
+    asd.set_client_version(fd.version.c_str(), change_bits);
+    asd.set_client_detected();
+    asd.client_inferred_service_id = APP_ID_SSH;
+}
+
+static void service_success(SshAppIdInfo& fd, const Packet& p, AppIdSession& asd,
+    AppidChangeBits& change_bits)
+{
+    int16_t group;
+    uint16_t port;
+    const SfIp* ip;
+
+    if (p.is_from_client())
+    {
+        ip = p.ptrs.ip_api.get_dst();
+        port = p.ptrs.dp;
+        group = p.get_egress_group();
+    }
+    else
+    {
+        ip = p.ptrs.ip_api.get_src();
+        port = p.ptrs.sp;
+        group = p.get_ingress_group();
+    }
+
+    asd.set_server_info(*ip, port, group);
+    asd.set_service_id(APP_ID_SSH, asd.get_odp_ctxt());
+    asd.set_application_ids_service(APP_ID_SSH, change_bits);
+    asd.set_service_vendor(fd.vendor.c_str(), change_bits);
+    asd.set_service_version(fd.version.c_str(), change_bits);
+    asd.set_service_detected();
+}
+
+static void handle_success(SshEventFlowData& data, SshEvent& event,
+    AppIdSession& asd, AppidChangeBits& change_bits)
+{
+    service_success(data.service_info, *event.get_packet(), asd, change_bits);
+    client_success(data.client_info, asd, change_bits);
+
+    if (appidDebug->is_active())
+        LogMessage("AppIdDbg %s SSH event handler service detected\n",
+            appidDebug->get_debug_session());
+}
+
+
+static void free_ssh_flow_data(void* data)
+{
+    delete (SshEventFlowData* )data;
+}
+
+unsigned int SshEventHandler::id;
+
+void SshEventHandler::handle(DataEvent& event, Flow* flow)
+{
+    if (!flow)
+        return;
+
+    AppIdSession* asd = appid_api.get_appid_session(*flow);
+    if (!asd)
+        return;
+
+    if (asd->get_odp_ctxt_version() != pkt_thread_odp_ctxt->get_version())
+        return; // Skip detection for sessions using old odp context after odp reload
+    if (!asd->get_session_flags(APPID_SESSION_DISCOVER_APP | APPID_SESSION_SPECIAL_MONITORED))
+        return;
+
+    SshEventFlowData* data = (SshEventFlowData* )asd->get_flow_data(id);
+
+    if (data and data->failed)
+    {
+        if (appidDebug->is_active())
+        {
+            LogMessage("AppIdDbg %s SSH detection failed, ignoring event\n",
+                appidDebug->get_debug_session());
+        }
+        return;
+    }
+
+    if (!data)
+    {
+        data = new SshEventFlowData;
+        asd->add_flow_data(data, id, &free_ssh_flow_data);
+    }
+
+    SshEvent& ssh_event = (SshEvent&)event;
+    SshAppIdInfo* fd;
+    if (ssh_event.get_direction() == PKT_FROM_SERVER)
+        fd = &data->service_info;
+    else
+        fd = &data->client_info;
+
+    if (fd->finished)
+        return;
+
+    switch(ssh_event.get_event_type())
+    {
+    case SSH_VERSION_STRING:
+        if (!handle_protocol(ssh_event, fd) and appidDebug->is_active())
+            LogMessage("AppIdDbg %s SSH event handler received unsupported protocol %s\n",
+                appidDebug->get_debug_session(), ssh_event.get_version_str().c_str());
+        break;
+
+    case SSH_VALIDATION:
+        AppidChangeBits change_bits;
+        switch (ssh_event.get_validation_result())
+        {
+        case SSH_VALID_KEXINIT:
+            if (appidDebug->is_active())
+                LogMessage("AppIdDbg %s SSH event handler received valid key exchange\n",
+                    appidDebug->get_debug_session());
+            fd->finished = true;
+            break;
+
+        case SSH_INVALID_KEXINIT:
+            if (appidDebug->is_active())
+                LogMessage("AppIdDbg %s SSH event handler received invalid key exchange\n",
+                    appidDebug->get_debug_session());
+            handle_failure(*asd, *data);
+            break;
+
+        case SSH_INVALID_VERSION:
+            if (appidDebug->is_active())
+                LogMessage("AppIdDbg %s SSH event handler received invalid version\n",
+                    appidDebug->get_debug_session());
+            handle_failure(*asd, *data);
+            break;
+
+        default:
+            break;
+        }
+
+        if (data->service_info.finished and data->client_info.finished)
+        {
+            handle_success(*data, ssh_event, *asd, change_bits);
+            asd->publish_appid_event(change_bits, *ssh_event.get_packet());
+        }
+        // Don't generate an event in case of failure. We want to give third-party a chance
+
+        break;
+    }
+}
similarity index 56%
rename from src/network_inspectors/appid/client_plugins/client_app_ssh.h
rename to src/network_inspectors/appid/appid_ssh_event_handler.h
index bcd72b2c9dac6f45a20b2380766c6cacce1d53c0..5ad70b0a533d1944c1039ce5946ad5e1422acb79 100644 (file)
@@ -1,6 +1,5 @@
 //--------------------------------------------------------------------------
-// Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
-// Copyright (C) 2005-2013 Sourcefire, Inc.
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
 //
 // This program is free software; you can redistribute it and/or modify it
 // under the terms of the GNU General Public License Version 2 as published
 // with this program; if not, write to the Free Software Foundation, Inc.,
 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //--------------------------------------------------------------------------
+// appid_ssh_event_handler.h  author Daniel McGarvey <danmcgar@cisco.com>
 
-// client_app_ssh.h author Sourcefire Inc.
+#ifndef APPID_SSH_EVENT_HANDLER_H
+#define APPID_SSH_EVENT_HANDLER_H
 
-#ifndef CLIENT_APP_SSH_H
-#define CLIENT_APP_SSH_H
+#include "pub_sub/ssh_events.h"
 
-#include "client_plugins/client_detector.h"
+#include "appid_module.h"
 
-class SshClientDetector : public ClientDetector
+class SshEventHandler : public snort::DataHandler
 {
 public:
-    SshClientDetector(ClientDiscovery*);
+    SshEventHandler() : snort::DataHandler(MOD_NAME) 
+    { id = snort::FlowData::create_flow_data_id(); }
 
-    int validate(AppIdDiscoveryArgs&) override;
+    void handle(snort::DataEvent &, snort::Flow *) override;
+
+private:
+    static unsigned int id;
+};
+
+struct SshAppIdInfo
+{
+    std::string vendor;
+    std::string version;
+    bool finished = false;
 };
-#endif
 
+struct SshEventFlowData
+{
+    SshAppIdInfo service_info;
+    SshAppIdInfo client_info;
+    bool failed = false;
+};
+
+#endif
diff --git a/src/network_inspectors/appid/client_plugins/client_app_ssh.cc b/src/network_inspectors/appid/client_plugins/client_app_ssh.cc
deleted file mode 100644 (file)
index 08bf3a8..0000000
+++ /dev/null
@@ -1,513 +0,0 @@
-//--------------------------------------------------------------------------
-// Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
-// Copyright (C) 2005-2013 Sourcefire, Inc.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License Version 2 as published
-// by the Free Software Foundation.  You may not use, modify or distribute
-// this program under any other version of the GNU General Public License.
-//
-// This program is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License along
-// with this program; if not, write to the Free Software Foundation, Inc.,
-// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-//--------------------------------------------------------------------------
-
-// client_app_ssh.cc author Sourcefire Inc.
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "client_app_ssh.h"
-
-#include <cstring>
-#include <string>
-
-#include "app_info_table.h"
-#include "application_ids.h"
-
-static const char SSH_CLIENT_BANNER[] = "SSH-";
-#define SSH_CLIENT_BANNER_LEN (sizeof(SSH_CLIENT_BANNER)-1)
-#define SSH_CLIENT_BANNER_MAXPOS (sizeof(SSH_CLIENT_BANNER)-2)
-
-static const char DROPBEAR_BANNER[] = "dropbear";
-#define DROPBEAR_BANNER_MAXPOS (sizeof(DROPBEAR_BANNER)-2)
-
-static const char LSH_BANNER[] = "lsh";
-#define LSH_BANNER_MAXPOS (sizeof(LSH_BANNER)-2)
-
-static const char OPENSSH_BANNER[] = "OpenSSH";
-#define OPENSSH_BANNER_MAXPOS (sizeof(OPENSSH_BANNER)-2)
-
-static const char PUTTY_BANNER[] = "PuTTY";
-#define PUTTY_BANNER_MAXPOS (sizeof(PUTTY_BANNER)-2)
-
-#define SSH_MSG_KEYXINIT            20
-#define SSH_MSG_IGNORE              2
-#define SSH_MSG_SESSION_KEY         3
-#define SSH_MAX_BANNER_LEN          255
-#define SSH2                        2
-#define SSH1                        1
-
-
-enum SSH2HeaderState
-{
-    SSH2_HEADER_BEGIN,
-    SSH2_HEADER_PLEN,
-    SSH2_HEADER_CODE,
-    SSH2_IGNORE,
-    SSH2_PADDING,
-    SSH2_KEYX_HEADER_FINISH,
-    SSH2_FIELD_LEN_BEGIN,
-    SSH2_FIELD_DATA_BEGIN,
-    SSH2_PAYLOAD_BEGIN
-};
-
-enum SSH1HeaderState
-{
-    SSH1_HEADER_BEGIN,
-    SSH1_HEADER_PLEN,
-    SSH1_HEADER_FIND_CODE,
-    SSH1_HEADER_CODE,
-    SSH1_SESSION_KEY
-};
-
-struct ClientSSHData
-{
-    SSH2HeaderState hstate;
-    SSH1HeaderState oldhstate;
-    unsigned len;
-    unsigned pos;
-    unsigned field;
-    unsigned field_len;
-    unsigned read_data;
-    union
-    {
-        uint32_t len;
-        uint8_t raw_len[4];
-    } l;
-    unsigned ssh_version;
-    uint8_t version[SSH_MAX_BANNER_LEN];
-    uint8_t plen;
-    uint8_t code;
-    uint32_t client_id;
-    uint8_t proto_string[SSH_MAX_BANNER_LEN];
-    uint8_t proto_strlen;
-    bool proto_string_done;
-};
-
-#pragma pack(1)
-
-struct ClientSSHKeyString
-{
-    uint32_t len;
-    uint8_t data;
-};
-
-struct ClientSSHMsg
-{
-    uint32_t len;
-    uint8_t plen;
-    uint8_t code;
-};
-
-struct ClientSSH2KeyExchange
-{
-    ClientSSHMsg msg;
-    uint8_t cookie[16];
-};
-
-struct ClientSSH1KeyExchangeV1
-{
-    uint32_t len;
-    uint8_t code;
-};
-
-struct ClientSSHKeyExchangeFinal
-{
-    uint8_t kex_pkt;
-    uint32_t future;
-};
-
-#pragma pack()
-
-SshClientDetector::SshClientDetector(ClientDiscovery* cdm)
-{
-    handler = cdm;
-    name = "SSH";
-    proto = IpProtocol::TCP;
-    minimum_matches = 1;
-    provides_user = true;
-
-    tcp_patterns =
-    {
-        { (const uint8_t*)SSH_CLIENT_BANNER, sizeof(SSH_CLIENT_BANNER) - 1,  0, 0, APP_ID_SSH },
-        { (const uint8_t*)OPENSSH_BANNER,    sizeof(OPENSSH_BANNER) - 1,    -1, 0,
-          APP_ID_OPENSSH },
-        { (const uint8_t*)PUTTY_BANNER,      sizeof(PUTTY_BANNER) - 1,      -1, 0, APP_ID_PUTTY },
-        { (const uint8_t*)LSH_BANNER,        sizeof(LSH_BANNER) - 1,         0, 0, APP_ID_LSH },
-        { (const uint8_t*)DROPBEAR_BANNER,   sizeof(DROPBEAR_BANNER) - 1,   -1, 0,
-          APP_ID_DROPBEAR },
-    };
-
-    appid_registry =
-    {
-        { APP_ID_DROPBEAR, APPINFO_FLAG_CLIENT_ADDITIONAL },
-        { APP_ID_SSH, APPINFO_FLAG_CLIENT_ADDITIONAL },
-        { APP_ID_LSH, APPINFO_FLAG_CLIENT_ADDITIONAL },
-        { APP_ID_PUTTY, APPINFO_FLAG_CLIENT_ADDITIONAL },
-        { APP_ID_OPENSSH, APPINFO_FLAG_CLIENT_ADDITIONAL }
-    };
-
-    handler->register_detector(name, this, proto);
-}
-
-
-static inline int ssh_client_validate_keyx(uint16_t offset, const uint8_t* data,
-    uint16_t size, ClientSSHData* fd)
-{
-    const ClientSSHMsg* ckx;
-    const ClientSSHKeyString* cks;
-    const ClientSSH2KeyExchange* ckex;
-
-    while (offset < size)
-    {
-        switch (fd->hstate)
-        {
-        case SSH2_HEADER_BEGIN:
-            fd->l.raw_len[fd->pos] = data[offset];
-            fd->pos++;
-            if (fd->pos == sizeof(ckx->len))
-            {
-                fd->len = ntohl(fd->l.len);
-                fd->hstate = SSH2_HEADER_PLEN;
-            }
-            break;
-        case SSH2_HEADER_PLEN:
-            fd->plen = data[offset];
-            fd->hstate = SSH2_HEADER_CODE;
-            fd->pos++;
-            break;
-        case SSH2_HEADER_CODE:
-            fd->code = data[offset];
-            if (fd->code == SSH_MSG_KEYXINIT)
-            {
-                fd->pos = 0;
-                fd->hstate = SSH2_KEYX_HEADER_FINISH;
-                fd->read_data = fd->plen + sizeof(ckex->cookie) + sizeof(ckx->len);
-            }
-            else if (fd->code == SSH_MSG_IGNORE)
-            {
-                fd->pos = sizeof(ckx->len) + 2;
-                fd->hstate = SSH2_IGNORE;
-            }
-            else
-                return APPID_EINVALID;
-            fd->len = ntohl(fd->l.len) + sizeof(ckx->len);
-            if (fd->len > 35000)
-                return APPID_EINVALID;
-            break;
-        case SSH2_IGNORE:
-            fd->pos++;
-            if (fd->pos >= fd->len)
-            {
-                fd->hstate = SSH2_HEADER_BEGIN;
-                fd->pos = 0;
-            }
-            break;
-        case SSH2_KEYX_HEADER_FINISH:
-            fd->pos++;
-            if (fd->pos >= sizeof(ckex->cookie))
-            {
-                fd->hstate = SSH2_FIELD_LEN_BEGIN;
-                fd->pos = 0;
-            }
-            break;
-        case SSH2_FIELD_LEN_BEGIN:
-            fd->l.raw_len[fd->pos] = data[offset];
-            fd->pos++;
-            if (fd->pos >= sizeof(cks->len))
-            {
-                fd->pos = 0;
-                fd->field_len = ntohl(fd->l.len);
-                fd->read_data += fd->field_len + sizeof(cks->len);
-                if (fd->read_data > fd->len)
-                    return APPID_EINVALID;
-                if (fd->field_len)
-                    fd->hstate = SSH2_FIELD_DATA_BEGIN;
-                else
-                {
-                    fd->field++;
-                    if (fd->field >= 10)
-                        fd->hstate = SSH2_PAYLOAD_BEGIN;
-                }
-            }
-            break;
-        case SSH2_FIELD_DATA_BEGIN:
-            fd->pos++;
-            if (fd->pos >= fd->field_len)
-            {
-                fd->field++;
-                if (fd->field >= 10)
-                    fd->hstate = SSH2_PAYLOAD_BEGIN;
-                else
-                    fd->hstate = SSH2_FIELD_LEN_BEGIN;
-                fd->pos = 0;
-            }
-            break;
-        case SSH2_PAYLOAD_BEGIN:
-            if (fd->pos >= offsetof(ClientSSHKeyExchangeFinal, future))
-            {
-                fd->l.raw_len[fd->pos - offsetof(ClientSSHKeyExchangeFinal, future)] =
-                    data[offset];
-            }
-            fd->pos++;
-            if (fd->pos >= sizeof(ClientSSHKeyExchangeFinal))
-            {
-                if (fd->l.len != 0)
-                    return APPID_EINVALID;
-                fd->hstate = SSH2_PADDING;
-                fd->pos = 0;
-            }
-            break;
-        case SSH2_PADDING:
-            fd->pos++;
-            if (fd->pos >= fd->plen)
-            {
-                offset++;
-
-                // FIXIT-L if offset > size then there is probably a D-H
-                // Key Exchange Init packet in this payload. For now parsing
-                // the Key Exchange Init is good enough to declare valid
-                // key exchange but for future enhance parsing to validate
-                // the D-H Key Exchange Init.
-
-                if (offset == size)
-                    return APPID_SUCCESS;
-                else
-                    return APPID_SUCCESS;
-            }
-            break;
-
-        default:
-            assert(0);  //  All cases should be handled above.
-        }
-        offset++;
-    }
-    return APPID_INPROCESS;
-}
-
-static inline int ssh_client_validate_pubkey(uint16_t offset, const uint8_t* data,
-    uint16_t size, ClientSSHData* fd)
-{
-    const ClientSSHMsg* ckx;
-
-    while (offset < size)
-    {
-        switch (fd->oldhstate)
-        {
-        case SSH1_HEADER_BEGIN:
-            fd->l.raw_len[fd->pos] = data[offset];
-            fd->pos++;
-            if (fd->pos == sizeof(ckx->len))
-            {
-                fd->len = ntohl(fd->l.len);
-                fd->oldhstate = SSH1_HEADER_PLEN;
-            }
-            break;
-        case SSH1_HEADER_PLEN:
-            if (size > (fd->len + sizeof(ckx->len)))
-                fd->plen = size - (fd->len + sizeof(ckx->len));
-            else
-                fd->plen = 0;
-            fd->oldhstate = SSH1_HEADER_FIND_CODE;
-            // fallthrough
-        case SSH1_HEADER_FIND_CODE:
-            if (fd->pos == fd->plen + sizeof(ckx->len))
-            {
-                fd->oldhstate = SSH1_HEADER_CODE;
-                fd->code = data[offset];
-            }
-            fd->pos++;
-            break;
-        case SSH1_HEADER_CODE:
-            if (fd->code == SSH_MSG_SESSION_KEY)
-            {
-                fd->oldhstate = SSH1_SESSION_KEY;
-                fd->pos++;
-            }
-            else
-                return APPID_EINVALID;
-            fd->len = fd->len + fd->plen + sizeof(ckx->len);
-            if (fd->len > 35000)
-                return APPID_EINVALID;
-            break;
-        case SSH1_SESSION_KEY:
-            fd->pos++;
-            if (fd->pos >= fd->len)
-            {
-                offset++;
-                if (offset == size)
-                    return APPID_SUCCESS;
-                return APPID_EINVALID;
-            }
-            break;
-        }
-        offset++;
-    }
-    return APPID_INPROCESS;
-}
-
-static inline int ssh_client_sm(AppIdDiscoveryArgs& args, ClientSSHData* fd)
-{
-    const char *pattern_begin;
-    const char *pattern_end;
-    uint8_t d;
-
-    if (strncmp((const char*)fd->proto_string, SSH_CLIENT_BANNER, SSH_CLIENT_BANNER_LEN) != 0)
-    {
-        return APPID_EINVALID;
-    }
-
-    d = fd->proto_string[SSH_CLIENT_BANNER_MAXPOS+1];
-    if (d == '1')
-        fd->ssh_version = SSH1;
-    else if (d == '2')
-        fd->ssh_version = SSH2;
-    else
-    {
-        return APPID_EINVALID;
-    }
-
-
-    pattern_begin = strchr((const char*)(fd->proto_string + SSH_CLIENT_BANNER_MAXPOS + 1), '-');
-    if (pattern_begin != nullptr)
-    {
-        pattern_begin++;
-    }
-    else
-    {
-        return APPID_EINVALID;
-    }
-
-    pattern_end = strpbrk(pattern_begin, "_-");
-    if (pattern_end != nullptr)
-    {
-        size_t pattern_len = (size_t)(pattern_end - pattern_begin);
-        string pattern(pattern_begin, pattern_len);
-        SshPatternMatchers& table = args.asd.get_odp_ctxt().get_ssh_matchers();
-
-        if (table.has_pattern(pattern))
-        {
-            fd->client_id = table.get_appid(pattern);
-        }
-        else
-        {
-            fd->client_id = APP_ID_SSH;
-        }
-        while (pattern_end - (const char*)fd->proto_string < fd->proto_strlen)
-        {
-            d = *pattern_end;
-            if (d == ' ' || d == '\n')
-            {
-                break;
-            }
-            if (d != '\r' && d != '-' && d != '_')
-            {
-                fd->version[fd->pos++] = d;
-            }
-            pattern_end++;
-        }
-    }
-    else
-    {
-        fd->client_id = APP_ID_SSH;
-    }
-    fd->pos = 0;
-    return APPID_INPROCESS;
-}
-
-int SshClientDetector::validate(AppIdDiscoveryArgs& args)
-{
-    ClientSSHData* fd;
-    int sm_ret = APPID_INPROCESS;
-
-    if (!args.size || args.dir != APP_ID_FROM_INITIATOR)
-        return APPID_INPROCESS;
-
-    fd = ( ClientSSHData*)data_get(args.asd);
-    if (!fd)
-    {
-        fd = (ClientSSHData*)snort_calloc(sizeof(ClientSSHData));
-        data_add(args.asd, fd, &snort_free);
-        fd->hstate = SSH2_HEADER_BEGIN;
-        fd->oldhstate = SSH1_HEADER_BEGIN;
-        fd->proto_string_done = false;
-    }
-
-    uint16_t offset = 0;
-    if (!(fd->proto_string_done))
-    {
-        const uint8_t *line_end = (const uint8_t *)memchr(args.data, '\n', args.size);
-        size_t append_len;
-        if (line_end != nullptr)
-        {
-            append_len = (size_t)(line_end - args.data + 1);
-            fd->proto_string_done = true;
-        }
-        else
-        {
-            append_len = args.size;
-        }
-
-        if (fd->proto_strlen + append_len < SSH_MAX_BANNER_LEN)
-        {
-            strncat((char*)fd->proto_string, (const char*)args.data, append_len);
-            fd->proto_strlen += append_len;
-        }
-        else
-        {
-            return APPID_EINVALID;
-        }
-
-        if (line_end == nullptr)
-        {
-            return APPID_INPROCESS;
-        }
-
-        fd->proto_string_done = true;
-        sm_ret = ssh_client_sm(args, fd);
-        if (sm_ret != APPID_INPROCESS)
-        {
-            return sm_ret;
-        }
-        offset = (uint16_t)(line_end - args.data + 1);
-    }
-
-    switch (fd->ssh_version)
-    {
-        case SSH2:
-            sm_ret = ssh_client_validate_keyx(offset, args.data, args.size, fd);
-            break;
-        case SSH1:
-            sm_ret = ssh_client_validate_pubkey(offset, args.data, args.size, fd);
-            break;
-        default:
-            sm_ret = APPID_EINVALID;
-            break;
-    }
-
-    if (sm_ret != APPID_SUCCESS)
-    {
-        return sm_ret;
-    }
-
-    add_app(args.asd, APP_ID_SSH, fd->client_id, (const char*)fd->version, args.change_bits);
-    return APPID_SUCCESS;
-}
-
index 9daa7a68eae670e912852f15d3dd298c6b73afa3..9ca686607225670a039a0f64ed952dc2e48d7478 100644 (file)
@@ -35,7 +35,6 @@
 #include "client_app_bit.h"
 #include "client_app_msn.h"
 #include "client_app_rtp.h"
-#include "client_app_ssh.h"
 #include "client_app_timbuktu.h"
 #include "client_app_tns.h"
 #include "client_app_vnc.h"
@@ -62,7 +61,6 @@ void ClientDiscovery::initialize(AppIdInspector& inspector)
     new SipTcpClientDetector(this);
     new SipUdpClientDetector(this);
     new SmtpClientDetector(this);
-    new SshClientDetector(this);
     new TimbuktuClientDetector(this);
     new TnsClientDetector(this);
     new VncClientDetector(this);
index bcb37e54aaaed774e3c19f344f14ed8af5da0036..451822e4493a127fdc709e0e33bd0a3ec0a6bef2 100644 (file)
@@ -43,11 +43,3 @@ AppId SshPatternMatchers::get_appid(const std::string& pattern) const
 {
     return ssh_patterns.at(pattern);
 }
-
-void SshPatternMatchers::finalize_patterns()
-{
-    ssh_patterns["dropbear"] = APP_ID_DROPBEAR;
-    ssh_patterns["OpenSSH"] = APP_ID_OPENSSH;
-    ssh_patterns["PuTTY"] = APP_ID_PUTTY;
-    ssh_patterns["lsh"] = APP_ID_LSH;
-}
index b10e338ffb2760e4bd48b2af9c965583ef117bd6..00dd5d77a93eaf996b938df656abff2faf945096 100644 (file)
@@ -16,7 +16,7 @@
 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //--------------------------------------------------------------------------
 
-// ssh_patterns.cc author Daniel McGarvey <danmcgar@cisco.com>
+// ssh_patterns.h author Daniel McGarvey <danmcgar@cisco.com>
 
 #ifndef SSH_PATTERNS_H
 #define SSH_PATTERNS_H
@@ -25,7 +25,7 @@
  * SshPatternMatchers is a wrapper around an unordered_map
  * which maps strings to AppIds. SSH Client Patterns
  * are registered through a lua API, and these mappings
- * are used by client_app_ssh.cc to identify clients.
+ * are used by AppId to identify clients.
  * An instance of the class is held by OdpContext.
  */
 
@@ -43,7 +43,6 @@ public:
     bool has_pattern(const std::string& pattern) const;
     bool empty() const;
     AppId get_appid(const std::string& pattern) const;
-    void finalize_patterns();
 private:
     SshPatternTable ssh_patterns;
 };
index ae28fa54d8e6ed0acfd248175b0a7b84215e4ff0..f038848ab816efc2cb7c39f3bba474330d13de24 100644 (file)
@@ -66,7 +66,6 @@
 #include "service_rsync.h"
 #include "service_rtmp.h"
 #include "service_snmp.h"
-#include "service_ssh.h"
 #include "service_ssl.h"
 #include "service_telnet.h"
 #include "service_tftp.h"
@@ -116,7 +115,6 @@ void ServiceDiscovery::initialize(AppIdInspector& inspector)
     new SipServiceDetector(this);
     new SmtpServiceDetector(this);
     new SnmpServiceDetector(this);
-    new SshServiceDetector(this);
     new SslServiceDetector(this);
     new TelnetServiceDetector(this);
     new TftpServiceDetector(this);
@@ -438,7 +436,7 @@ int ServiceDiscovery::identify_service(AppIdSession& asd, Packet* p,
                 asd.service_detector = sds->get_service();
             /* If we've gotten to brute force, give next detector a try. */
             else if ( sds_state == ServiceState::SEARCHING_BRUTE_FORCE and
-                      asd.service_candidates.empty() )
+                asd.service_candidates.empty() )
             {
                 asd.service_detector = sds->select_detector_by_brute_force(proto,
                     asd.get_odp_ctxt().get_service_disco_mgr());
@@ -511,7 +509,18 @@ int ServiceDiscovery::identify_service(AppIdSession& asd, Packet* p,
         /* If we tried everything and found nothing, then fail. */
         if ( asd.service_candidates.empty() and ret != APPID_SUCCESS and
              ( asd.service_search_state == SESSION_SERVICE_SEARCH_STATE::PENDING ) )
-            got_fail_service = true;
+        {
+            // FIXIT-E: For now, wait for snort service inspection only for TCP. In the future,
+            // if AppId rolls any of its UDP detectors into service inspectors, the UDP condition
+            // below needs to be removed.
+            if (asd.has_no_service_inspector() or (proto == IpProtocol::UDP))
+                got_fail_service = true;
+            else if (appidDebug->is_active() and !asd.service_detector and !asd.has_no_service_candidate())
+                LogMessage("AppIdDbg %s No service candidate, wait for snort service inspection\n",
+                    appidDebug->get_debug_session());
+
+            asd.set_no_service_candidate();
+        }
     }
 
     /* Failed all candidates, or no detector identified after seeing bidirectional exchange */
@@ -524,7 +533,7 @@ int ServiceDiscovery::identify_service(AppIdSession& asd, Packet* p,
         // Don't log this if fail service is not due to empty list
         if (appidDebug->is_active() and !(got_fail_service and asd.service_detector))
             LogMessage("AppIdDbg %s No service %s\n", appidDebug->get_debug_session(),
-                got_fail_service? "candidate" : "detector");
+                got_fail_service ? "candidate" : "detector");
         got_fail_service = true;
         fail_service(asd, p, dir, nullptr, sds);
         ret = APPID_NOMATCH;
@@ -568,7 +577,7 @@ void ServiceDiscovery::clear_ftp_service_state()
 bool ServiceDiscovery::do_service_discovery(AppIdSession& asd, Packet* p,
     AppidSessionDirection direction, AppidChangeBits& change_bits)
 {
-    bool isTpAppidDiscoveryDone = false;
+    bool is_discovery_done = false;
     uint32_t prev_service_state = asd.service_disco_state;
     AppId tp_app_id = asd.get_tp_app_id();
 
@@ -670,7 +679,7 @@ bool ServiceDiscovery::do_service_discovery(AppIdSession& asd, Packet* p,
     if (asd.service_disco_state == APPID_DISCO_STATE_STATEFUL)
     {
         identify_service(asd, p, direction, change_bits);
-        isTpAppidDiscoveryDone = true;
+        is_discovery_done = true;
         //to stop executing validator after service has been detected
         if (asd.get_session_flags(APPID_SESSION_SERVICE_DETECTED |
             APPID_SESSION_CONTINUE) == APPID_SESSION_SERVICE_DETECTED)
@@ -691,7 +700,7 @@ bool ServiceDiscovery::do_service_discovery(AppIdSession& asd, Packet* p,
         {
                 asd.stop_service_inspection(p, direction);
                 asd.set_service_id(APP_ID_UNKNOWN, asd.get_odp_ctxt());
-                return isTpAppidDiscoveryDone;
+                return is_discovery_done;
         }
 
         AppIdDnsSession* dsession = asd.get_dns_session();
@@ -718,7 +727,7 @@ bool ServiceDiscovery::do_service_discovery(AppIdSession& asd, Packet* p,
         }
     }
 
-    return isTpAppidDiscoveryDone;
+    return is_discovery_done;
 }
 
 /**Called when service can not be identified on a flow but the checks failed on client request
diff --git a/src/network_inspectors/appid/service_plugins/service_ssh.cc b/src/network_inspectors/appid/service_plugins/service_ssh.cc
deleted file mode 100644 (file)
index 5e44361..0000000
+++ /dev/null
@@ -1,555 +0,0 @@
-//--------------------------------------------------------------------------
-// Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
-// Copyright (C) 2005-2013 Sourcefire, Inc.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License Version 2 as published
-// by the Free Software Foundation.  You may not use, modify or distribute
-// this program under any other version of the GNU General Public License.
-//
-// This program is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License along
-// with this program; if not, write to the Free Software Foundation, Inc.,
-// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-//--------------------------------------------------------------------------
-
-// service_ssh.cc author Sourcefire Inc.
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "service_ssh.h"
-
-#include "app_info_table.h"
-#include "application_ids.h"
-
-#define SSH_PORT    22
-
-#define SSH_BANNER "SSH-"
-#define SERVICE_SSH_MSG_KEYXINIT 20
-#define SERVICE_SSH_MSG_IGNORE 2
-#define SERVICE_SSH_MSG_PUBLIC_KEY 2
-#define SERVICE_SSH_KEY_STRINGS 10
-#define SSH_MAX_FIELDS 10
-#define SSH_MAX_BANNER_LENGTH 255
-
-#define SSH_VERSION_2    2
-#define SSH_VERSION_1    1
-#define MINIMUM_SSH_VERS_LEN    4
-
-enum SSHState
-{
-    SSH_STATE_BANNER,
-    SSH_STATE_KEY,
-    SSH_STATE_DONE
-};
-
-enum SSHHeaderState
-{
-    SSH_HEADER_BEGIN,
-    SSH_HEADER_PLEN,
-    SSH_HEADER_CODE,
-    SSH_IGNORE,
-    SSH_PADDING,
-    SSH_KEYX_HEADER_FINISH,
-    SSH_FIELD_LEN_BEGIN,
-    SSH_FIELD_DATA_BEGIN,
-    SSH_PAYLOAD_BEGIN
-};
-
-enum OldSSHHeaderState
-{
-    OLD_SSH_HEADER_BEGIN,
-    OLD_SSH_HEADER_PLEN,
-    OLD_SSH_HEADER_FIND_CODE,
-    OLD_SSH_HEADER_CODE,
-    OLD_SSH_PUBLIC_KEY
-};
-
-struct ServiceSSHData
-{
-    SSHState state;
-    SSHHeaderState hstate;
-    OldSSHHeaderState oldhstate;
-    unsigned len;
-    unsigned pos;
-    unsigned field;
-    unsigned field_len;
-    unsigned read_data;
-    union
-    {
-        uint32_t len;
-        uint8_t raw_len[4];
-    } l;
-    char* vendor;
-    char* version;
-    unsigned ssh_version;
-    uint8_t plen;
-    uint8_t code;
-};
-
-#pragma pack(1)
-
-struct ServiceSSHKeyString
-{
-    uint32_t len;
-    uint8_t data;
-};
-
-struct ServiceSSHMsg
-{
-    uint32_t len;
-    uint8_t plen;
-    uint8_t code;
-};
-
-struct ServiceSSHKeyExchange
-{
-    ServiceSSHMsg msg;
-    uint8_t cookie[16];
-};
-
-struct ServiceSSHKeyExchangeV1
-{
-    uint32_t len;
-    uint8_t code;
-};
-
-struct ServiceSSHKeyExchangeFinal
-{
-    uint8_t kex_pkt;
-    uint32_t future;
-};
-
-#pragma pack()
-
-SshServiceDetector::SshServiceDetector(ServiceDiscovery* sd)
-{
-    handler = sd;
-    name = "ssh";
-    proto = IpProtocol::TCP;
-    detectorType = DETECTOR_TYPE_DECODER;
-
-    tcp_patterns =
-    {
-        { (const uint8_t*)SSH_BANNER, sizeof(SSH_BANNER) - 1, 0, 0, 0 },
-    };
-
-    appid_registry =
-    {
-        { APP_ID_SSH, APPINFO_FLAG_SERVICE_ADDITIONAL }
-    };
-
-    service_ports =
-    {
-        { SSH_PORT, IpProtocol::TCP, false }
-    };
-
-    handler->register_detector(name, this, proto);
-}
-
-
-static int ssh_validate_pubkey(const uint8_t* data, uint16_t size, ServiceSSHData* ss)
-{
-    uint16_t offset = 0;
-    const ServiceSSHMsg* skx;
-
-    while (offset < size)
-    {
-        switch (ss->oldhstate)
-        {
-        case OLD_SSH_HEADER_BEGIN:
-            ss->l.raw_len[ss->pos] = data[offset];
-            ss->pos++;
-            if (ss->pos == sizeof(skx->len))
-            {
-                ss->len = ntohl(ss->l.len);
-                ss->oldhstate = OLD_SSH_HEADER_PLEN;
-            }
-            break;
-        case OLD_SSH_HEADER_PLEN:
-            if (size > (ss->len + sizeof(skx->len)))
-                ss->plen = size - (ss->len + sizeof(skx->len));
-            else
-                ss->plen = 0;
-            ss->oldhstate = OLD_SSH_HEADER_FIND_CODE;
-            // fallthrough
-        case OLD_SSH_HEADER_FIND_CODE:
-            if (ss->pos == ss->plen + sizeof(skx->len))
-            {
-                ss->oldhstate = OLD_SSH_HEADER_CODE;
-                ss->code = data[offset];
-            }
-            ss->pos++;
-            break;
-        case OLD_SSH_HEADER_CODE:
-            if (ss->code == SERVICE_SSH_MSG_PUBLIC_KEY)
-            {
-                ss->oldhstate = OLD_SSH_PUBLIC_KEY;
-                ss->pos++;
-            }
-            else
-                return APPID_NOMATCH;
-            ss->len = ss->len + ss->plen + sizeof(skx->len);
-            if (ss->len > 35000)
-                return APPID_NOMATCH;
-            break;
-        case OLD_SSH_PUBLIC_KEY:
-            ss->pos++;
-            if (ss->pos >= ss->len)
-            {
-                offset++;
-                if (offset == size)
-                    return APPID_SUCCESS;
-                return APPID_NOMATCH;
-            }
-            break;
-        }
-        offset++;
-    }
-    return APPID_INPROCESS;
-}
-
-static int ssh_validate_keyx(const uint8_t* data, uint16_t size, ServiceSSHData* ss)
-{
-    uint16_t offset = 0;
-    const ServiceSSHMsg* skx;
-    const ServiceSSHKeyString* sks;
-    const ServiceSSHKeyExchange* skex;
-
-    while (offset < size)
-    {
-        switch (ss->hstate)
-        {
-        case SSH_HEADER_BEGIN:
-            ss->l.raw_len[ss->pos] = data[offset];
-            ss->pos++;
-            if (ss->pos == sizeof(skx->len))
-            {
-                ss->len = ntohl(ss->l.len);
-                ss->hstate = SSH_HEADER_PLEN;
-            }
-            break;
-        case SSH_HEADER_PLEN:
-            ss->plen = data[offset];
-            ss->hstate = SSH_HEADER_CODE;
-            ss->pos++;
-            break;
-        case SSH_HEADER_CODE:
-            ss->code = data[offset];
-            if (ss->code == SERVICE_SSH_MSG_KEYXINIT)
-            {
-                ss->pos = 0;
-                ss->hstate = SSH_KEYX_HEADER_FINISH;
-                ss->read_data = ss->plen + sizeof(skex->cookie) + sizeof(skx->len);
-            }
-            else if (ss->code == SERVICE_SSH_MSG_IGNORE)
-            {
-                ss->pos = sizeof(skx->len) + 2;
-                ss->hstate = SSH_IGNORE;
-            }
-            else
-                return APPID_NOMATCH;
-            ss->len = ntohl(ss->l.len) + sizeof(skx->len);
-            if (ss->len > 35000)
-                return APPID_NOMATCH;
-            break;
-        case SSH_IGNORE:
-            ss->pos++;
-            if (ss->pos >= ss->len)
-            {
-                ss->hstate = SSH_HEADER_BEGIN;
-                ss->pos = 0;
-            }
-            break;
-        case SSH_KEYX_HEADER_FINISH:
-            ss->pos++;
-            if (ss->pos >= sizeof(skex->cookie))
-            {
-                ss->hstate = SSH_FIELD_LEN_BEGIN;
-                ss->pos = 0;
-            }
-            break;
-        case SSH_FIELD_LEN_BEGIN:
-            ss->l.raw_len[ss->pos] = data[offset];
-            ss->pos++;
-            if (ss->pos >= sizeof(sks->len))
-            {
-                ss->pos = 0;
-                ss->field_len = ntohl(ss->l.len);
-                ss->read_data += ss->field_len + sizeof(sks->len);
-                if (ss->read_data > ss->len)
-                    return APPID_NOMATCH;
-                if (ss->field_len)
-                    ss->hstate = SSH_FIELD_DATA_BEGIN;
-                else
-                {
-                    ss->field++;
-                    if (ss->field >= 10)
-                        ss->hstate = SSH_PAYLOAD_BEGIN;
-                }
-            }
-            break;
-        case SSH_FIELD_DATA_BEGIN:
-            ss->pos++;
-            if (ss->pos >= ss->field_len)
-            {
-                ss->field++;
-                if (ss->field >= 10)
-                    ss->hstate = SSH_PAYLOAD_BEGIN;
-                else
-                    ss->hstate = SSH_FIELD_LEN_BEGIN;
-                ss->pos = 0;
-            }
-            break;
-        case SSH_PAYLOAD_BEGIN:
-            if (ss->pos >= offsetof(ServiceSSHKeyExchangeFinal, future))
-            {
-                ss->l.raw_len[ss->pos - offsetof(ServiceSSHKeyExchangeFinal, future)] =
-                    data[offset];
-            }
-            ss->pos++;
-            if (ss->pos >= sizeof(ServiceSSHKeyExchangeFinal))
-            {
-                if (ss->l.len != 0)
-                    return APPID_NOMATCH;
-                ss->hstate = SSH_PADDING;
-                ss->pos = 0;
-            }
-            break;
-        case SSH_PADDING:
-            ss->pos++;
-            if (ss->pos >= ss->plen)
-            {
-                offset++;
-                if (offset == size)
-                    return APPID_SUCCESS;
-                return APPID_NOMATCH;
-            }
-            break;
-        }
-        offset++;
-    }
-    return APPID_INPROCESS;
-}
-
-static void ssh_free_state(void* data)
-{
-    ServiceSSHData* sd = (ServiceSSHData*)data;
-
-    if (sd)
-    {
-        if (sd->vendor)
-        {
-            snort_free(sd->vendor);
-            sd->vendor = nullptr;
-        }
-        if (sd->version)
-        {
-            snort_free(sd->version);
-            sd->version = nullptr;
-        }
-        snort_free(sd);
-    }
-}
-
-int SshServiceDetector::validate(AppIdDiscoveryArgs& args)
-{
-    ServiceSSHData* ss;
-    uint16_t offset;
-    int retval;
-    const char* ven;
-    const char* ver;
-    const char* end;
-    unsigned len;
-    int client_major;
-    const uint8_t* data = args.data;
-    uint16_t size = args.size;
-
-    if (!size)
-        goto inprocess;
-
-    ss = (ServiceSSHData*)data_get(args.asd);
-    if (!ss)
-    {
-        ss = (ServiceSSHData*)snort_calloc(sizeof(ServiceSSHData));
-        data_add(args.asd, ss, &ssh_free_state);
-        ss->state = SSH_STATE_BANNER;
-        ss->hstate = SSH_HEADER_BEGIN;
-        ss->oldhstate = OLD_SSH_HEADER_BEGIN;
-    }
-
-    if (args.dir != APP_ID_FROM_RESPONDER)
-    {
-        if (!ss->ssh_version)
-        {
-            if ((size_t)size > (sizeof(SSH_BANNER)-1+MINIMUM_SSH_VERS_LEN) &&
-                !strncmp(SSH_BANNER, (const char*)data, sizeof(SSH_BANNER)-1))
-            {
-                data += (sizeof(SSH_BANNER)-1);
-                if (!isdigit(*data))
-                    goto not_compatible;
-                else
-                    client_major = *data;
-                data++;
-                if (*data != '.')
-                    goto not_compatible;
-                switch (client_major)
-                {
-                case 0x31:
-                    if (*(data+1) == 0x39 && *(data+2) == 0x39)
-                        ss->ssh_version = SSH_VERSION_2;
-                    else
-                        ss->ssh_version = SSH_VERSION_1;
-                    break;
-                case 0x32:
-                    ss->ssh_version = SSH_VERSION_2;
-                    break;
-                default:
-                    goto not_compatible;
-                }
-            }
-        }
-        goto inprocess;
-    }
-
-    switch (ss->state)
-    {
-    case SSH_STATE_BANNER:
-        offset = 0;
-        ss->state = SSH_STATE_KEY;
-        for (;; )
-        {
-            /* SSH-v-\n where v is at least 1 character */
-            if ((size_t)(size-offset) < ((sizeof(SSH_BANNER)-1)+3))
-            {
-                goto fail;
-            }
-            if (!strncmp(SSH_BANNER, (const char*)data+offset, sizeof(SSH_BANNER)-1))
-            {
-                unsigned blen = sizeof(SSH_BANNER)-1;
-                offset += sizeof(SSH_BANNER)-1;
-                for (;
-                    offset<size && blen<=SSH_MAX_BANNER_LENGTH;
-                    offset++, blen++)
-                {
-                    if (data[offset] == '-')
-                        break;
-                    if (!isprint(data[offset]) || isspace(data[offset]))
-                    {
-                        goto fail;
-                    }
-                }
-                offset++;
-                blen++;
-                if (offset >= size || blen > SSH_MAX_BANNER_LENGTH)
-                {
-                    goto fail;
-                }
-                ven = (const char*)&data[offset];
-                for (;
-                    offset<size && blen<=SSH_MAX_BANNER_LENGTH;
-                    offset++, blen++)
-                {
-                    if (data[offset] == 0x0D || data[offset] == 0x0A)
-                    {
-                        if (data[offset] == 0x0D)
-                        {
-                            if (offset+1 >= size)
-                                goto fail;
-                            if (data[offset+1] != 0x0A)
-                                goto fail;
-                        }
-                        end = (const char*)&data[offset];
-                        if (ven == end)
-                            goto inprocess;
-                        for (ver=ven; ver < end && *ver && *ver != '_' && *ver != '-'; ver++)
-                            ;
-                        if (ver < (end - 1) && isdigit(*(ver+1)))
-                        {
-                            len = ver - ven;
-                            ss->vendor = (char*)snort_alloc(len+1);
-                            memcpy(ss->vendor, ven, len);
-                            ss->vendor[len] = 0;
-                            ver++;
-                            len = end - ver;
-                            ss->version = (char*)snort_alloc(len+1);
-                            memcpy(ss->version, ver, len);
-                            ss->version[len] = 0;
-                        }
-                        else
-                        {
-                            len = end - ven;
-                            ss->version = (char*)snort_calloc(len+1);
-                            memcpy(ss->version, ven, len);
-                            ss->version[len] = 0;
-                        }
-                        goto inprocess;
-                    }
-                    else if (!isprint(data[offset]))
-                        goto fail;
-                }
-                goto fail;
-            }
-            else
-            {
-                for (; offset<size; offset++)
-                {
-                    if (data[offset] == 0x0a)
-                    {
-                        offset++;
-                        break;
-                    }
-                }
-            }
-        }
-        break;
-    case SSH_STATE_KEY:
-        switch (ss->ssh_version)
-        {
-        case SSH_VERSION_2:
-            retval = ssh_validate_keyx(data, size, ss);
-            break;
-        case SSH_VERSION_1:
-            retval = ssh_validate_pubkey(data, size, ss);
-            break;
-        default:
-            goto fail;
-        }
-        goto done;
-    default:
-        break;
-    }
-    goto fail;
-
-done:
-    switch (retval)
-    {
-    case APPID_INPROCESS:
-inprocess:
-        service_inprocess(args.asd, args.pkt, args.dir);
-        return APPID_INPROCESS;
-
-    case APPID_SUCCESS:
-        return add_service(args.change_bits, args.asd, args.pkt, args.dir, APP_ID_SSH,
-            ss->vendor, ss->version, nullptr);
-
-    case APPID_NOMATCH:
-fail:
-        fail_service(args.asd, args.pkt, args.dir);
-        return APPID_NOMATCH;
-
-not_compatible:
-        incompatible_data(args.asd, args.pkt, args.dir);
-        return APPID_NOT_COMPATIBLE;
-
-    default:
-        return retval;
-    }
-}
-
index e3ba2d87398656e61a915dc317339d162ef698d3..c5b6ee2a338065c0b6a30a7a8fae3f8955a91588 100644 (file)
@@ -453,6 +453,11 @@ void Stuff::apply_service(Flow& flow)
     }
     else if (wizard)
         flow.set_clouseau(wizard);
+    else if (!flow.flags.svc_event_generated)
+    {
+        DataBus::publish(FLOW_NO_SERVICE_EVENT, DetectionEngine::get_current_packet());
+        flow.flags.svc_event_generated = true;
+    }
 }
 
 void Stuff::apply_assistant(Flow& flow, const char* service)
index d825fc45c78d7d1165d71a7e1788312f5523553a..10ddbec074cde2700cbafaaec63468926b5bd005 100644 (file)
@@ -14,6 +14,7 @@ set (PUB_SUB_INCLUDES
     opportunistic_tls_event.h
     sip_events.h
     smb_events.h
+    ssh_events.h
 )
 
 add_library( pub_sub OBJECT
diff --git a/src/pub_sub/ssh_events.h b/src/pub_sub/ssh_events.h
new file mode 100644 (file)
index 0000000..48d4a20
--- /dev/null
@@ -0,0 +1,73 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// ssh_events.h author Daniel McGarvey <danmcgar@cisco.com>
+
+#ifndef SSH_EVENTS_H
+#define SSH_EVENTS_H
+
+// This event allows the SSH service inspector to publish extracted metadata
+// for use by data bus subscribers
+
+#include "service_inspectors/ssh/ssh.h"
+
+#define SSH_EVENT "ssh_event"
+
+enum SshEventType {
+    SSH_VERSION_STRING,
+    SSH_VALIDATION
+};
+
+enum SshValidationResult {
+    SSH_NOT_FINISHED,
+    SSH_VALID_KEXINIT,
+    SSH_INVALID_VERSION,
+    SSH_INVALID_KEXINIT
+};
+
+class SshEvent : public snort::DataEvent
+{
+public:
+    SshEvent(const SshEventType event_type, const SshValidationResult result,
+        const std::string& version_str, const uint8_t direction, const snort::Packet* packet) :
+        event_type(event_type), result(result), version_str(version_str), direction(direction), packet(packet)
+        { }
+
+    SshEventType get_event_type() const
+    { return event_type; }
+
+    SshValidationResult get_validation_result() const
+    { return result; }
+
+    const std::string& get_version_str() const
+    { return version_str; }
+
+    uint8_t get_direction() const
+    { return direction; }
+
+    const snort::Packet* get_packet() override
+    { return packet; }
+
+private:
+    const SshEventType event_type;
+    const SshValidationResult result;
+    const std::string version_str;
+    const uint8_t direction;
+    const snort::Packet* packet;
+};
+
+#endif
index 90d060bcaaa2898f2ab0191927291174f7772b6e..8f482cc9c877b0f00338694540f9513251f65b66 100644 (file)
@@ -34,6 +34,7 @@
 #include "log/messages.h"
 #include "profiler/profiler.h"
 #include "protocols/packet.h"
+#include "pub_sub/ssh_events.h"
 #include "stream/stream.h"
 
 #include "ssh_module.h"
@@ -48,9 +49,10 @@ THREAD_LOCAL SshStats sshstats;
  * Function prototype(s)
  */
 static void snort_ssh(SSH_PROTO_CONF* GlobalConf, Packet* p);
-static unsigned int ProcessSSHProtocolVersionExchange(SSH_PROTO_CONF*, SSHData*, Packet*, uint8_t);
-static unsigned int ProcessSSHKeyExchange(SSHData*, Packet*, uint8_t, unsigned int);
-static unsigned int ProcessSSHKeyInitExchange(SSHData*, Packet*, uint8_t, unsigned int);
+static bool process_ssh_version_string(SSH_PROTO_CONF* config, SSHData* sessionp, Packet* p, uint8_t direction);
+static bool process_ssh1_key_exchange(SSHData *sessionp, Packet *p, uint8_t direction);
+static bool process_ssh2_kexinit(SSHData *sessionp, Packet *p, uint8_t direction);
+static bool process_ssh2_key_exchange(SSHData *sessionp, Packet *p, uint8_t direction);
 
 unsigned SshFlowData::inspector_id = 0;
 
@@ -58,7 +60,7 @@ SshFlowData::SshFlowData() : FlowData(inspector_id)
 {
     session = {};
     sshstats.concurrent_sessions++;
-    if(sshstats.max_concurrent_sessions < sshstats.concurrent_sessions)
+    if (sshstats.max_concurrent_sessions < sshstats.concurrent_sessions)
         sshstats.max_concurrent_sessions = sshstats.concurrent_sessions;
 }
 
@@ -81,38 +83,7 @@ SSHData* get_session_data(const Flow* flow)
     return fd ? &fd->session : nullptr;
 }
 
-/* Returns the true length of the ssh packet, including
- * the ssh packet header and all padding.
- *
- * If the packet length is invalid, 0 is returned.
- * The return value is never larger than buflen.
- *
- * PARAMETERS:
- * p: Pointer to the SSH packet.
- * buflen: the size of packet buffer.
-*/
-static unsigned int SSHPacket_GetLength(const SSH2Packet* p, size_t buflen)
-{
-    unsigned int ssh_length;
-
-    if (buflen < sizeof(SSH2Packet))
-        return 0;
-
-    ssh_length = ntohl(p->packet_length);
-    if ((ssh_length < sizeof(SSH2Packet) + 1) || ssh_length > SSH2_PACKET_MAX_SIZE)
-        return 0;
-
-    /* Everything after packet length field (including padding) is included in the packet_length */
-    ssh_length += sizeof(p->packet_length);
-
-    if (buflen < ssh_length)
-        return buflen; /* truncated */
-
-    return ssh_length;
-}
-
 // Main runtime entry point for SSH inspector.
-
 static void snort_ssh(SSH_PROTO_CONF* config, Packet* p)
 {
     Profile profile(sshPerfStats);
@@ -128,7 +99,7 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p)
     // means we've already missed packets) set missed packets flag and make
     // sure we don't do any more reassembly on this session
     if ( p->test_session_flags(SSNFLAG_MIDSTREAM)
-        || Stream::missed_packets(p->flow, SSN_DIR_BOTH) )
+        or Stream::missed_packets(p->flow, SSN_DIR_BOTH) )
     {
         // Order only matters if the packets are not encrypted
         if ( !(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED ))
@@ -140,6 +111,7 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p)
     sshstats.total_bytes += p->dsize;
 
     uint8_t direction;
+    uint8_t pkt_direction;
     uint32_t search_dir_ver;
     uint32_t search_dir_keyinit;
 
@@ -147,57 +119,77 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p)
     if ( p->is_from_server() )
     {
         direction = SSH_DIR_FROM_SERVER;
+        pkt_direction = PKT_FROM_SERVER;
         search_dir_ver = SSH_FLG_SERV_IDSTRING_SEEN;
         search_dir_keyinit = SSH_FLG_SERV_PKEY_SEEN | SSH_FLG_SERV_KEXINIT_SEEN;
     }
     else
     {
         direction = SSH_DIR_FROM_CLIENT;
+        pkt_direction = PKT_FROM_CLIENT;
         search_dir_ver = SSH_FLG_CLIENT_IDSTRING_SEEN;
         search_dir_keyinit = SSH_FLG_CLIENT_SKEY_SEEN | SSH_FLG_CLIENT_KEXINIT_SEEN;
     }
 
-    unsigned int offset = 0;
-
-    if ( !(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED ))
+    if (!(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED))
     {
         // If server and client have not performed the protocol
         // version exchange yet, must look for version strings.
-        if ( !(sessp->state_flags & search_dir_ver) )
+        if (!(sessp->state_flags & search_dir_ver))
         {
-            offset = ProcessSSHProtocolVersionExchange(config, sessp, p, direction);
-            if (!offset)
-                // Error processing protovers exchange msg
-                return;
-
-            // found protocol version.
-            // Stream reassembly might have appended an ssh packet,
-            // such as the key exchange init.
-            // Thus call ProcessSSHKeyInitExchange() too.
+            bool valid_version = process_ssh_version_string(config, sessp, p, direction);
+            if (valid_version)
+            {
+                std::string proto_string((const char *)(p->data), p->dsize);
+                SshEvent event(SSH_VERSION_STRING, SSH_NOT_FINISHED, proto_string, pkt_direction, p);
+                DataBus::publish(SSH_EVENT, event, p->flow);
+            }
+            else
+            {
+                SshEvent event(SSH_VALIDATION, SSH_INVALID_VERSION, "", pkt_direction, p);
+                DataBus::publish(SSH_EVENT, event, p->flow);
+            }
         }
-
-        // Expecting to see the key init exchange at this point
-        // (in SSH2) or the actual key exchange if SSH1
-        if ( !(sessp->state_flags & search_dir_keyinit) )
+        else if (!(sessp->state_flags & search_dir_keyinit))
         {
-            offset = ProcessSSHKeyInitExchange(sessp, p, direction, offset);
-
-            if (!offset)
+            bool keyx_valid = false;
+            switch (sessp->version)
+            {
+            case SSH_VERSION_1:
+                keyx_valid = process_ssh1_key_exchange(sessp, p, direction);
+                break;
+            case SSH_VERSION_2:
+                keyx_valid = process_ssh2_kexinit(sessp, p, direction);
+                break;
+            default:
+                // key exchange packet sent before version was determined
+                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_VERSION);
+                break;
+            }
+            if (keyx_valid)
+            {
+                SshEvent event(SSH_VALIDATION, SSH_VALID_KEXINIT, "", pkt_direction, p);
+                DataBus::publish(SSH_EVENT, event, p->flow);
+            }
+            else
             {
-                if ( !(sessp->state_flags & SSH_FLG_SESS_ENCRYPTED ))
-                    return;
+                SshEvent event(SSH_VALIDATION, SSH_INVALID_KEXINIT, "", pkt_direction, p);
+                DataBus::publish(SSH_EVENT, event, p->flow);
+                sessp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
             }
         }
-
-        // If SSH2, need to process the actual key exchange msgs.
-        // The actual key exchange type was negotiated in the
-        // key exchange init msgs. SSH1 won't arrive here.
-        offset = ProcessSSHKeyExchange(sessp, p, direction, offset);
-        if (!offset)
-            return;
+        else
+        {
+            bool keyx_valid = process_ssh2_key_exchange(sessp, p, direction);
+            // FIXIT-M
+            // Originally, appid only looked at the kexinit packet for validation.
+            // We may want to produce an additional event for validation of the
+            // entire key exchange.
+            if (!keyx_valid)
+                sessp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
+        }
     }
-
-    if ( (sessp->state_flags & SSH_FLG_SESS_ENCRYPTED ))
+    else
     {
         // Traffic on this session is currently encrypted.
         // Two of the major SSH exploits, SSH1 CRC-32 and
@@ -211,25 +203,18 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p)
         {
             if ( direction == SSH_DIR_FROM_CLIENT )
             {
-                if (!offset)
-                    sessp->num_client_bytes += p->dsize;
-
-                else
-                    sessp->num_client_bytes += (p->dsize - offset);
-
+                sessp->num_client_bytes += p->dsize;
                 if ( sessp->num_client_bytes >= config->MaxClientBytes )
                 {
                     // Probable exploit in progress.
                     if (sessp->version == SSH_VERSION_1)
                         DetectionEngine::queue_event(GID_SSH, SSH_EVENT_CRC32);
-
                     else
                         DetectionEngine::queue_event(GID_SSH, SSH_EVENT_RESPOVERFLOW);
 
                     Stream::stop_inspection(p->flow, p, SSN_DIR_BOTH, -1, 0);
                 }
             }
-
             else
             {
                  // Have seen a server response, so this appears to be a valid
@@ -237,7 +222,6 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p)
                 sessp->num_client_bytes = 0;
             }
         }
-
         else
         {
             // Have already examined more than the limit
@@ -251,80 +235,59 @@ static void snort_ssh(SSH_PROTO_CONF* config, Packet* p)
     }
 }
 
-/* Checks if the string 'str' is 'max' bytes long or longer.
- * Returns 0 if 'str' is less than or equal to 'max' bytes;
- * returns 1 otherwise.
-*/
-
-static inline int SSHCheckStrlen(const char* str, int max)
-{
-    if ( memchr(str, '\0', max) )
-        return 0;           /* str size is <= max bytes */
-
-    return 1;
-}
-
-/* Attempts to process current packet as a protocol version exchange
- * packet. This function will be called if either the client or server
- * protocol version message (or both) has not been sent.
- *
- * PARAMETERS:
- *
- * sessionp:    Pointer to SSH data for packet's session.
- * p:    Pointer to the packet to inspect.
- * direction:     Which direction the packet is going.
- *
- * RETURNS:  offset processed
- */
-static unsigned int ProcessSSHProtocolVersionExchange(SSH_PROTO_CONF* config, SSHData* sessionp,
-    Packet* p, uint8_t direction)
+static bool process_ssh_version_string(
+    SSH_PROTO_CONF* config, SSHData* sessionp, Packet* p, uint8_t direction)
 {
-    const char* version_stringp = (const char*)p->data;
-    const char* version_end;
-
-    /* Get the version. */
-    if ( p->dsize >= 6 &&
-        !strncasecmp(version_stringp, "SSH-1.", 6))
+    if (p->dsize > config->MaxServerVersionLen)
     {
-        if (( p->dsize > 7 ) && ( version_stringp[6] == '9')
-            && (version_stringp[7] == '9'))
+        DetectionEngine::queue_event(GID_SSH, SSH_EVENT_SECURECRT);
+        // SSH_MAX_BANNER_LEN is 255, the maximum specified by the SSH protocol.
+        // MaxServerVersionLen defaults to 80, 
+        // but there may be valid version strings that are longer due to comments.
+        if (p->dsize > SSH_MAX_BANNER_LEN)
         {
-            /* SSH 1.99 which is the same as SSH2.0 */
-            sessionp->version = SSH_VERSION_2;
+            return false;
         }
-        else
+    }
+    if (p->dsize < SSH_MIN_BANNER_LEN 
+        or memcmp(p->data, SSH_BANNER, sizeof(SSH_BANNER)-1) != 0)
+    {
+        // according to the SSH specification,
+        // the server can send lines before the version string
+        // as long as they don't start with "SSH-",
+        // so we will ignore them.
+        return true;
+    }
+    const char *proto_ver = (const char *)p->data + sizeof(SSH_BANNER) - 1;
+    const char *proto_ver_end = (const char *)memchr(proto_ver, '-', p->dsize - sizeof(SSH_BANNER));
+    if (!proto_ver_end)
+    {
+        DetectionEngine::queue_event(GID_SSH, SSH_EVENT_VERSION);
+        return false;
+    }
+    
+    if (proto_ver[0] == '2' and proto_ver[1] == '.')
+    {
+        sessionp->version = SSH_VERSION_2;
+    }
+    else if (proto_ver[0] == '1' and proto_ver[1] == '.')
+    {
+        // version 1.99 == compatibility mode for 2.0
+        // determine version from client in this case
+        if (direction == SSH_DIR_FROM_CLIENT)
         {
             sessionp->version = SSH_VERSION_1;
         }
-
-        /* CAN-2002-0159 */
-        /* Verify the version string is not greater than
-         * the configured maximum.
-         * We've already verified the first 6 bytes, so we'll start
-         * check from &version_string[6] */
-        /* First make sure the data itself is sufficiently large */
-        if ((p->dsize > config->MaxServerVersionLen) &&
-            /* CheckStrlen will check if the version string up to
-             * MaxServerVersionLen+1 since there's no reason to
-             * continue checking after that point*/
-            (SSHCheckStrlen(&version_stringp[6], config->MaxServerVersionLen-6)))
+        else if (proto_ver[2] != '9' or proto_ver[3] != '9')
         {
-            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_SECURECRT);
+            sessionp->version = SSH_VERSION_1;
         }
     }
-    else if ( p->dsize >= 6 &&
-        !strncasecmp(version_stringp, "SSH-2.", 6))
-    {
-        sessionp->version = SSH_VERSION_2;
-    }
     else
     {
-        /* unknown version */
-        sessionp->version =  SSH_VERSION_UNKNOWN;
-
         DetectionEngine::queue_event(GID_SSH, SSH_EVENT_VERSION);
-
-        return 0;
+        sessionp->version = SSH_VERSION_UNKNOWN;
+        return false;
     }
 
     /* Saw a valid protocol exchange message. Mark the session
@@ -339,359 +302,236 @@ static unsigned int ProcessSSHProtocolVersionExchange(SSH_PROTO_CONF* config, SS
         sessionp->state_flags |= SSH_FLG_CLIENT_IDSTRING_SEEN;
         break;
     }
+    return true;
 
-    version_end = (const char*)memchr(version_stringp, '\n', p->dsize);
-    if (version_end)
-        return ((version_end - version_stringp) + 1);
-    /* incomplete version string, should end with \n or \r\n for sshv2 */
-    return p->dsize;
 }
 
-/* Called to process SSH1 key exchange or SSH2 key exchange init
- * messages.  On failure, inspection will be continued, but the packet
- * will be alerted on, and ignored.
- *
- * PARAMETERS:
- *
- * sessionp:    Pointer to SSH data for packet's session.
- * p:    Pointer to the packet to inspect.
- * direction:     Which direction the packet is going.
- *
- * RETURNS:  offset processed
- */
-static unsigned int ProcessSSHKeyInitExchange(SSHData* sessionp, Packet* p,
-    uint8_t direction, unsigned int offset)
+static bool process_ssh1_key_exchange(SSHData *sessionp, Packet *p, uint8_t direction)
 {
-    const SSH2Packet* ssh2p = nullptr;
-    uint16_t dsize = p->dsize;
-    const unsigned char* data = p->data;
-    unsigned int ssh_length = 0;
-
-    if (dsize < sizeof(SSH2Packet) || (dsize < (offset + sizeof(SSH2Packet)))
-        || (dsize <= offset))
-        return 0;
-
-    dsize -= offset;
-    data += offset;
-
-    if ( sessionp->version == SSH_VERSION_1 )
+    if (p->dsize < SSH1_KEYX_MIN_SIZE)
     {
-        uint32_t length;
-        uint8_t padding_length;
-        uint8_t message_type;
-
-        /*
-         * Validate packet data.
-         * First 4 bytes should have the SSH packet length,
-         * minus any padding.
-         */
-        if ( dsize < 4 )
-        {
-            {
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
-            }
+        DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+        return false;
+    }
+    uint32_t payload_length = ntohl(*(const uint32_t *)(p->data));
+    uint8_t padding = 8 - (payload_length % 8);
+    uint8_t code = p->data[sizeof(uint32_t) + padding];
 
-            return 0;
-        }
 
-        /*
-         * SSH1 key exchange is very simple and
-          * consists of only two messages, a server
-         * key and a client key message.`
-         */
-        memcpy(&length, data, sizeof(length));
-        length = ntohl(length);
+    if (p->dsize != sizeof(uint32_t) + padding + payload_length
+        or p->dsize > SSH_PACKET_MAX_SIZE)
+    {
+        DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+        return false;
+    }
 
-        /* Packet data should be larger than length, due to padding. */
-        if ( dsize < length )
+    switch (code)
+    {
+    case SSH_MSG_V1_SMSG_PUBLIC_KEY:
+        if (direction == SSH_DIR_FROM_SERVER)
         {
-            {
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
-            }
-
-            return 0;
+            sessionp->state_flags |= SSH_FLG_SERV_PKEY_SEEN;
         }
-
-        padding_length = (uint8_t)(8 - (length % 8));
-
-        /*
-         * With the padding calculated, verify data is sufficiently large
-         * to include the message type.
-         */
-        if ( dsize < (padding_length + 4 + 1 + offset))
-        {
-            if (offset == 0)
-            {
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
-            }
-
-            return 0;
+        else
+        { 
+            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+            return false;
         }
-
-        message_type = *( (const uint8_t*)(data + padding_length + 4));
-
-        switch ( message_type )
+        break;
+    case SSH_MSG_V1_CMSG_SESSION_KEY:
+        if (direction == SSH_DIR_FROM_CLIENT)
         {
-        case SSH_MSG_V1_SMSG_PUBLIC_KEY:
-            if ( direction == SSH_DIR_FROM_SERVER )
-            {
-                sessionp->state_flags |=
-                    SSH_FLG_SERV_PKEY_SEEN;
-            }
-            else
-            {
-                /* Server msg not from server. */
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
-            }
-            break;
-        case SSH_MSG_V1_CMSG_SESSION_KEY:
-            if ( direction == SSH_DIR_FROM_CLIENT )
-            {
-                sessionp->state_flags |=
-                    SSH_FLG_CLIENT_SKEY_SEEN;
-            }
-            else
-            {
-                /* Client msg not from client. */
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
-            }
-            break;
-        default:
-            /* Invalid msg type*/
-            break;
+            sessionp->state_flags |= SSH_FLG_CLIENT_SKEY_SEEN;
         }
-
-        /* Once the V1 key exchange is done, remainder of
-         * communications are encrypted.
-         */
-        ssh_length = length + padding_length + sizeof(length) + offset;
-
-        if ( (sessionp->state_flags & SSH_FLG_V1_KEYEXCH_DONE) ==
-            SSH_FLG_V1_KEYEXCH_DONE )
+        else
         {
-            sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
+            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+            return false;
         }
+        break;
     }
-    else if ( sessionp->version == SSH_VERSION_2 )
+    if ((sessionp->state_flags & SSH_FLG_V1_KEYEXCH_DONE) == SSH_FLG_V1_KEYEXCH_DONE)
     {
-        /* We want to overlay the data on our data packet struct,
-         * so first verify that the data size is big enough.
-         * This may legitimately occur such as in the case of a
-         * retransmission.
-         */
-        if ( dsize < sizeof(SSH2Packet) )
-        {
-            return 0;
-        }
-
-        /* Overlay the SSH2 binary data packet struct on the packet */
-        ssh2p = (const SSH2Packet*)data;
-        if ( dsize < SSH2_HEADERLEN + 1)
-        {
-            /* Invalid packet length. */
-
-            return 0;
-        }
-
-        ssh_length = offset + ntohl(ssh2p->packet_length) + sizeof(ssh2p->packet_length);
+        sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
+    }
+    return true;
+}
 
-        switch ( data[SSH2_HEADERLEN] )
+static bool process_ssh2_kexinit(SSHData *sessionp, Packet *p, uint8_t direction)
+{
+    uint16_t dsize = p->dsize;
+    unsigned int ssh_length = 0;
+    if (dsize < sizeof(SSH2KeyExchange) or dsize > SSH_PACKET_MAX_SIZE)
+    {
+        DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+        return false;
+    }
+    const SSH2KeyExchange* ssh_pkt = (const SSH2KeyExchange*)p->data;
+    ssh_length = ntohl(ssh_pkt->msg.len) + sizeof(uint32_t);
+    if (ssh_length != dsize)
+    {
+        DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+        return false;
+    }
+    switch(ssh_pkt->msg.code)
+    {
+    case SSH_MSG_KEXINIT:
+        sessionp->state_flags |=
+            (direction == SSH_DIR_FROM_SERVER ?
+            SSH_FLG_SERV_KEXINIT_SEEN :
+            SSH_FLG_CLIENT_KEXINIT_SEEN);
+        break;
+    case SSH_MSG_IGNORE:
+        return true;
+    default:
+        return false;
+    }
+    uint16_t total_length = sizeof(SSH2KeyExchange);
+    const uint8_t *data = p->data + sizeof(SSH2KeyExchange);
+    for (int i = 0; i < NUM_KEXINIT_LISTS; i++)
+    {
+        uint32_t list_length = ntohl(*((const uint32_t*)data)) + sizeof(uint32_t);
+        if (list_length > ssh_length or total_length + list_length > ssh_length)
         {
-        case SSH_MSG_KEXINIT:
-            sessionp->state_flags |=
-                (direction == SSH_DIR_FROM_SERVER ?
-                SSH_FLG_SERV_KEXINIT_SEEN :
-                SSH_FLG_CLIENT_KEXINIT_SEEN );
-            break;
-        default:
-            /* Unrecognized message type. */
-            break;
+            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+            return false;
         }
+        total_length += list_length;
+        data += list_length;
     }
-    else
+    total_length += sizeof(SSHKeyExchangeFinal) + ssh_pkt->msg.plen;
+    if (total_length != ssh_length)
     {
-        return 0;
+        DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+        return false;
     }
-
-    if (ssh_length < p->dsize)
-        return ssh_length;
-    else
-        return 0;
+    const SSHKeyExchangeFinal* final = (const SSHKeyExchangeFinal*)data;
+    if (final->future)
+    {
+        // using an unsupported future version
+        return false;
+    }
+    return true;
 }
 
-/* Called to process SSH2 key exchange msgs (key exch init msgs already
- * processed earlier). On failure, inspection will be continued, but the
- * packet will be alerted on, and ignored.
- *
- * PARAMETERS:
- *
- * sessionp:    Pointer to SSH data for packet's session.
- * p:    Pointer to the packet to inspect.
- * direction:     Which direction the packet is going.
- *
- * RETURNS:  offset processed
- */
-static unsigned int ProcessSSHKeyExchange(SSHData* sessionp, Packet* p,
-    uint8_t direction, unsigned int offset)
+static bool process_ssh2_key_exchange(SSHData *sessionp, Packet *p, uint8_t direction)
 {
     uint16_t dsize = p->dsize;
-    const unsigned char* data = p->data;
-    bool next_packet = true;
-    unsigned int npacket_offset = 0;
+    const unsigned char *data = p->data;
 
-    if (dsize < sizeof(SSH2Packet) || (dsize < (offset + sizeof(SSH2Packet)))
-        || (dsize <= offset))
+    if (dsize < sizeof(SSH2Packet))
     {
-        return 0;
+        return false;
     }
 
-    dsize -= offset;
-    data += offset;
+    const SSH2Packet *ssh2p = (const SSH2Packet *)data;
+    unsigned ssh_length = ntohl(ssh2p->packet_length) + sizeof(uint32_t);
 
-    while (next_packet)
+    if (ssh_length < sizeof(SSH2Packet) 
+        or ssh_length != dsize
+        or ssh_length > SSH_PACKET_MAX_SIZE)
     {
-        const SSH2Packet* ssh2p = (const SSH2Packet*)(data + npacket_offset);
-        unsigned ssh_length = SSHPacket_GetLength(ssh2p, dsize);
+        /* Invalid packet length. */
+        DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
+        return false;
+    }
 
-        if (ssh_length == 0)
+    switch (ssh2p->packet_data)
+    {
+    case SSH_MSG_KEXDH_INIT:
+        if (direction == SSH_DIR_FROM_CLIENT)
         {
-            if ( sessionp->state_flags & SSH_FLG_SESS_ENCRYPTED )
-            {
-                return ( npacket_offset + offset );
-            }
-            {
-                /* Invalid packet length. */
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_PAYLOAD_SIZE);
-            }
-
-            return 0;
+            sessionp->state_flags |=
+                SSH_FLG_KEXDH_INIT_SEEN;
         }
-
-        switch (data[npacket_offset + SSH2_HEADERLEN] )
+        else
         {
-        case SSH_MSG_KEXDH_INIT:
-            if ( direction == SSH_DIR_FROM_CLIENT )
-            {
-                sessionp->state_flags |=
-                    SSH_FLG_KEXDH_INIT_SEEN;
-            }
-            else
-            {
-                /* Client msg from server. */
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
-            }
-            break;
-        case SSH_MSG_KEXDH_REPLY:
-            if ( direction == SSH_DIR_FROM_SERVER )
-            {
-                /* KEXDH_REPLY has the same msg
-                  * type as the new style GEX_REPLY
-                 */
-                sessionp->state_flags |=
-                    SSH_FLG_KEXDH_REPLY_SEEN |
-                    SSH_FLG_GEX_REPLY_SEEN;
-            }
-            else
-            {
-                /* Server msg from client. */
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
-            }
-            break;
-        case SSH_MSG_KEXDH_GEX_REQ:
-            if ( direction == SSH_DIR_FROM_CLIENT )
-            {
-                sessionp->state_flags |=
-                    SSH_FLG_GEX_REQ_SEEN;
-            }
-            else
-            {
-                /* Server msg from client. */
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
-            }
-            break;
-        case SSH_MSG_KEXDH_GEX_GRP:
-            if ( direction == SSH_DIR_FROM_SERVER )
-            {
-                sessionp->state_flags |=
-                    SSH_FLG_GEX_GRP_SEEN;
-            }
-            else
-            {
-                /* Client msg from server. */
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
-            }
-            break;
-        case SSH_MSG_KEXDH_GEX_INIT:
-            if ( direction == SSH_DIR_FROM_CLIENT )
-            {
-                sessionp->state_flags |=
-                    SSH_FLG_GEX_INIT_SEEN;
-            }
-            else
-            {
-                /* Server msg from client. */
-                DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
-            }
-            break;
-        case SSH_MSG_NEWKEYS:
-            /* This message is required to complete the
-             * key exchange. Both server and client should
-             * send one, but as per Alex Kirk's note on this,
-             * in some implementations the server does not
-             * actually send this message. So receiving a new
-             * keys msg from the client is sufficient.
+            /* Client msg from server. */
+            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+        }
+        break;
+    case SSH_MSG_KEXDH_REPLY:
+        if (direction == SSH_DIR_FROM_SERVER)
+        {
+            /* KEXDH_REPLY has the same msg
+             * type as the new style GEX_REPLY
              */
-            if ( direction == SSH_DIR_FROM_CLIENT )
-            {
-                sessionp->state_flags |= SSH_FLG_CLIENT_NEWKEYS_SEEN;
-            }
-            else
-            {
-                sessionp->state_flags |= SSH_FLG_SERVER_NEWKEYS_SEEN;
-            }
-            break;
-        default:
-            /* Unrecognized message type. Possibly encrypted */
-            sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
-            return ( npacket_offset + offset);
+            sessionp->state_flags |=
+                SSH_FLG_KEXDH_REPLY_SEEN |
+                SSH_FLG_GEX_REPLY_SEEN;
         }
-
-        /* If either an old-style or new-style Diffie Helman exchange
-         * has completed, the session will enter encrypted mode.
-         */
-        if (( (sessionp->state_flags &
-            SSH_FLG_V2_DHOLD_DONE) == SSH_FLG_V2_DHOLD_DONE )
-            || ( (sessionp->state_flags &
-            SSH_FLG_V2_DHNEW_DONE) == SSH_FLG_V2_DHNEW_DONE ))
+        else
         {
-            sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
-            if (ssh_length < dsize)
-            {
-                if ( ssh_length >= 4 )
-                {
-                    npacket_offset += ssh_length;
-                    dsize -= ssh_length;
-                    continue;
-                }
-                return ( npacket_offset + offset );
-            }
-            else
-                return 0;
+            /* Server msg from client. */
+            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
         }
-
-        if ((ssh_length < dsize) && (ssh_length >= 4))
+        break;
+    case SSH_MSG_KEXDH_GEX_REQ:
+        if (direction == SSH_DIR_FROM_CLIENT)
+        {
+            sessionp->state_flags |=
+                SSH_FLG_GEX_REQ_SEEN;
+        }
+        else
+        {
+            /* Server msg from client. */
+            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+        }
+        break;
+    case SSH_MSG_KEXDH_GEX_GRP:
+        if (direction == SSH_DIR_FROM_SERVER)
         {
-            npacket_offset += ssh_length;
-            dsize -= ssh_length;
+            sessionp->state_flags |=
+                SSH_FLG_GEX_GRP_SEEN;
         }
         else
         {
-            next_packet = false;
-            npacket_offset = 0;
+            /* Client msg from server. */
+            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
         }
+        break;
+    case SSH_MSG_KEXDH_GEX_INIT:
+        if (direction == SSH_DIR_FROM_CLIENT)
+        {
+            sessionp->state_flags |=
+                SSH_FLG_GEX_INIT_SEEN;
+        }
+        else
+        {
+            /* Server msg from client. */
+            DetectionEngine::queue_event(GID_SSH, SSH_EVENT_WRONGDIR);
+        }
+        break;
+    case SSH_MSG_NEWKEYS:
+        /* This message is required to complete the
+         * key exchange. Both server and client should
+         * send one, but as per Alex Kirk's note on this,
+         * in some implementations the server does not
+         * actually send this message. So receiving a new
+         * keys msg from the client is sufficient.
+         */
+        if (direction == SSH_DIR_FROM_CLIENT)
+        {
+            sessionp->state_flags |= SSH_FLG_CLIENT_NEWKEYS_SEEN;
+        }
+        else
+        {
+            sessionp->state_flags |= SSH_FLG_SERVER_NEWKEYS_SEEN;
+        }
+        break;
+    default:
+        return false;
     }
 
-    return (npacket_offset + offset);
+    /* If either an old-style or new-style Diffie Helman exchange
+     * has completed, the session will enter encrypted mode.
+     */
+    if (((sessionp->state_flags & SSH_FLG_V2_DHOLD_DONE) == SSH_FLG_V2_DHOLD_DONE)
+        or ((sessionp->state_flags & SSH_FLG_V2_DHNEW_DONE) == SSH_FLG_V2_DHNEW_DONE))
+    {
+        sessionp->state_flags |= SSH_FLG_SESS_ENCRYPTED;
+    }
+    return true;
 }
 
 //-------------------------------------------------------------------------
index 6753d0ef14bbaaf71a3a0f083aca373f693a3f5f..f3484bf799e20b3dfeb1c44c49f7baed9b5d9361 100644 (file)
@@ -111,6 +111,7 @@ public:
     SSHData session;
 };
 
+#define SSH_BANNER "SSH-"
 // Length of SSH2 header, in bytes.
 #define SSH2_HEADERLEN      (5)
 // Length of SSH2 Padding, in bytes.
@@ -118,24 +119,63 @@ public:
 // Length of SSH2 packet, in bytes.
 #define SSH2_PACKET_LEN    (SSH2_HEADERLEN - SSH2_PADDING_LEN)
 #define SSH2_PACKET_MAX_SIZE    (256 * 1024)
+#define SSH_PACKET_MAX_SIZE 35000
+#define SSH_MAX_BANNER_LEN 255
+#define SSH2_COOKIE_SIZE 16
+#define NUM_KEXINIT_LISTS 10
+#define SSH_MIN_BANNER_LEN 9 //SSH-2.0-*\n
+#define SSH1_KEYX_MIN_SIZE (4 + 8 + 1) // length + padding + message
+
+#pragma pack(1)
+struct SSHKeyString
+{
+    uint32_t len;
+    uint8_t data;
+};
+
+struct SSHMsg
+{
+    uint32_t len;
+    uint8_t plen;
+    uint8_t code;
+};
+
+struct SSH2KeyExchange
+{
+    SSHMsg msg;
+    uint8_t cookie[16];
+};
+
+struct SSH1KeyExchangeV1
+{
+    uint32_t len;
+    uint8_t code;
+};
+
+struct SSHKeyExchangeFinal
+{
+    uint8_t kex_pkt;
+    uint32_t future;
+};
 
 struct SSH2Packet
 {
     uint32_t packet_length; // Length not including this field or the mesg auth code (mac)
     uint8_t padding_length; // Length of padding section.
-    char packet_data[1];    // Variable length packet payload + padding + MAC.
+    uint8_t packet_data;    // Variable length packet payload + padding + MAC.
 };
+#pragma pack()
 
 // SSH v1 message types (of interest)
 #define SSH_MSG_V1_SMSG_PUBLIC_KEY  2
 #define SSH_MSG_V1_CMSG_SESSION_KEY 3
 
 // SSH v2 message types (of interest)
+#define SSH_MSG_IGNORE      2
 #define SSH_MSG_KEXINIT     20
 #define SSH_MSG_NEWKEYS     21
 #define SSH_MSG_KEXDH_INIT  30
 #define SSH_MSG_KEXDH_REPLY 31
-
 #define SSH_MSG_KEXDH_GEX_REQ   34
 #define SSH_MSG_KEXDH_GEX_GRP   33
 #define SSH_MSG_KEXDH_GEX_INIT  32
index e3fbf6aac8efc639626675cd7f9e9dcfbf548e9e..74e6d528c0d56b6eb8ca1141783347d2ca02ceeb 100644 (file)
 
 using namespace snort;
 
-SshSplitter::SshSplitter(bool c2s) : StreamSplitter(c2s)
-{
-    client_remain_bytes = 0;
-    server_remain_bytes = 0;
-    state = 0;
-}
-
 StreamSplitter::Status SshSplitter::ssh2_key_exchange_scan(
     const uint8_t* data, uint32_t len, uint32_t* fp,
     uint32_t& remain_bytes)
 {
     if (remain_bytes < len)
     {
-        uint32_t offset = remain_bytes;
-        while (offset < len)
+        if (remain_bytes != 0)
         {
-            const SSH2Packet* sshp = (const SSH2Packet*)(data + offset);
-            uint32_t ssh_len = ntohl(sshp->packet_length);
-            if (ssh_len > (len - offset))
-            {
-                remain_bytes = ssh_len - (len - SSH2_PACKET_LEN);
-                return StreamSplitter::SEARCH;
-            }
-
-            switch (data[offset + SSH2_HEADERLEN])
-            {
-            case SSH_MSG_KEXDH_GEX_INIT:
-            case SSH_MSG_KEXDH_GEX_GRP:
-            case SSH_MSG_KEXDH_GEX_REQ:
-            case SSH_MSG_KEXDH_REPLY:
-            case SSH_MSG_KEXDH_INIT:
-            case SSH_MSG_KEXINIT:
-                offset += (ssh_len + SSH2_PACKET_LEN);
-                break;
-            case SSH_MSG_NEWKEYS:
-                offset += (ssh_len + SSH2_PACKET_LEN);
-            // fallthrough
-            default:
-                goto exit_loop;
-            }
+            *fp = remain_bytes;
+            return StreamSplitter::FLUSH;
+        }
+        const SSH2Packet* sshp = (const SSH2Packet*)data;
+        uint32_t ssh_len = ntohl(sshp->packet_length) + SSH2_PACKET_LEN;
+        if (ssh_len > len)
+        {
+            remain_bytes = ssh_len - len;
+            return StreamSplitter::SEARCH;
+        }
+        else
+        {
+            *fp = ssh_len;
+            return StreamSplitter::FLUSH;
         }
-exit_loop:
-        *fp = offset;
-        return StreamSplitter::FLUSH;
     }
     else
     {
@@ -91,7 +70,7 @@ StreamSplitter::Status SshSplitter::ssh2_scan(SSHData* sessp,
 {
     if (flags & PKT_FROM_SERVER)
     {
-        // Do not scan if sever new keys message seen.
+        // Do not scan if server new keys message seen.
         if (sessp->state_flags & SSH_FLG_SERVER_NEWKEYS_SEEN)
         {
             return SEARCH;
index 2aa543edc07c8b76a7ae1c8aa3afe6978d51202f..8234e86da5364d2f8319cee4583867b787df4942 100644 (file)
@@ -36,7 +36,8 @@ enum SshPafState
 class SshSplitter : public snort::StreamSplitter
 {
 public:
-    SshSplitter(bool c2s);
+    SshSplitter(bool c2s) : StreamSplitter(c2s)
+    { }
 
     Status scan(snort::Packet*, const uint8_t* data, uint32_t len,
         uint32_t flags, uint32_t* fp) override;
@@ -52,7 +53,7 @@ private:
     Status ssh2_scan(SSHData* sessp, const uint8_t* data, uint32_t len,
         uint32_t flags, uint32_t* fp);
 
-    uint32_t state;
+    SshPafState state = SSH_PAF_VER_EXCHANGE;
     uint32_t client_remain_bytes = 0;
     uint32_t server_remain_bytes = 0;
 };
index 16f02ea8e294aee2da51bad219b84aeb863bd2cf..703392a8dcfbeab8987e276cd8535f8389557459 100644 (file)
@@ -205,6 +205,11 @@ StreamSplitter::Status MagicSplitter::scan(
         count_miss(pkt->flow);
         trace_logf(wizard_trace, pkt, "%s streaming search abandoned\n", to_server() ? "c2s" : "s2c");
         wizard_processed_bytes = 0;
+        if (!pkt->flow->flags.svc_event_generated)
+        {
+            DataBus::publish(FLOW_NO_SERVICE_EVENT, pkt);
+            pkt->flow->flags.svc_event_generated = true;
+        }
         return ABORT;
     }
 
@@ -212,6 +217,19 @@ StreamSplitter::Status MagicSplitter::scan(
     if ( wand.spell )
         bookmark = wand.spell->book.get_bookmark();
 
+    // FIXIT-L Ideally, this event should be raised after wizard aborts its search. However, this
+    // could take multiple packets because wizard needs wizard.max_search_depth payload bytes before
+    // it aborts. This is an issue for AppId which consumes this event. AppId is required to declare
+    // unknown service as soon as it can so that the flow actions (such as IPS block, etc) don't get
+    // delayed. Because AppId depends on wizard only for SSH detection and SSH inspector can be
+    // attached very early, event is raised here after first scan. In the future, wizard should be
+    // enhanced to abort sooner if it can't detect service.
+    if (!pkt->flow->service and !pkt->flow->flags.svc_event_generated)
+    {
+        DataBus::publish(FLOW_NO_SERVICE_EVENT, pkt);
+        pkt->flow->flags.svc_event_generated = true;
+    }
+
     // ostensibly continue but splitter will be swapped out upon hit
     return SEARCH;
 }