srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/include/wireframe/l7/ssh.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'include/wireframe/l7/ssh.hpp')
-rw-r--r--include/wireframe/l7/ssh.hpp65
1 files changed, 65 insertions, 0 deletions
diff --git a/include/wireframe/l7/ssh.hpp b/include/wireframe/l7/ssh.hpp
new file mode 100644
index 0000000..efa471f
--- /dev/null
+++ b/include/wireframe/l7/ssh.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <cstdint>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+
+#include "wireframe/l7/dissector.hpp"
+
+// SSH's identification exchange (RFC 4253 section 4.2) is the one part
+// of an SSH connection sent in the clear, before key exchange starts
+// encrypting everything: both sides open with a single line of the
+// form "SSH-protoversion-softwareversion[ comments]" terminated by
+// CR LF (a bare LF is tolerated too, same leniency this project's HTTP
+// dissector already uses). Only that first line is ever readable --
+// everything after key exchange is opaque, so this dissector only ever
+// has one line to look at, on either side of the connection.
+namespace wireframe::net {
+
+inline constexpr std::uint16_t kSshPort = 22;
+
+struct SshBanner {
+ std::string proto_version;
+ std::string software_version;
+};
+
+inline std::optional<SshBanner> parse_ssh_banner(std::span<const unsigned char> payload) {
+ std::string_view text(reinterpret_cast<const char*>(payload.data()), payload.size());
+ if (text.substr(0, 4) != "SSH-") return std::nullopt;
+
+ std::size_t line_end = text.find("\r\n");
+ if (line_end == std::string_view::npos) {
+ line_end = text.find('\n');
+ if (line_end == std::string_view::npos) return std::nullopt;
+ }
+ std::string_view line = text.substr(4, line_end - 4); // past "SSH-"
+
+ std::size_t dash = line.find('-');
+ if (dash == std::string_view::npos) return std::nullopt;
+
+ SshBanner banner;
+ banner.proto_version = std::string(line.substr(0, dash));
+
+ // The software version runs up to the first space (start of an
+ // optional comment) or the end of the line, whichever is first.
+ std::string_view rest = line.substr(dash + 1);
+ std::size_t space = rest.find(' ');
+ banner.software_version = std::string(space == std::string_view::npos ? rest
+ : rest.substr(0, space));
+ return banner;
+}
+
+class SshDissector : public L7Dissector {
+public:
+ std::uint16_t port() const override { return kSshPort; }
+
+ std::optional<std::string> summarize(std::span<const unsigned char> payload) const override {
+ auto banner = parse_ssh_banner(payload);
+ if (!banner) return std::nullopt;
+ return "SSH " + banner->proto_version + " " + banner->software_version;
+ }
+};
+
+} // namespace wireframe::net