srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/include/wireframe/l7/ssh.hpp
blob: efa471f77c39252ac6efd58ac02737569ffe8458 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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