#include "ssh_hosts.h" #include namespace roro::term { namespace { std::vector linesOf(const std::string& text) { std::vector out; for (size_t at = 0; at < text.size();) { size_t end = text.find('\n', at); if (end == std::string::npos) end = text.size(); if (end > at) out.push_back(text.substr(at, end - at)); at = end + 1; } return out; } bool hostChars(const std::string& s) { if (s.empty() || s.size() > 253) return false; for (char c : s) if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '-')) return false; return s.front() != '.' && s.front() != '-'; } } // namespace std::string SshTarget::text() const { return user + "@" + host + (port == 22 ? "" : ":" + std::to_string(port)); } std::string SshTarget::hostPort() const { return host + ":" + std::to_string(port); } std::string parseSshTarget(const std::string& text, SshTarget& out) { size_t at = text.find('@'); if (at == std::string::npos || at == 0) return "user@host, please"; SshTarget t; t.user = text.substr(0, at); std::string rest = text.substr(at + 1); if (t.user.size() > 32 || t.user.find_first_of(" @:/") != std::string::npos) return "that isn't a user name"; size_t colon = rest.rfind(':'); if (colon != std::string::npos) { std::string port = rest.substr(colon + 1); long n = 0; if (port.empty() || port.size() > 5) return "a port from 1 to 65535"; for (char c : port) { if (c < '0' || c > '9') return "a port from 1 to 65535"; n = n * 10 + (c - '0'); } if (n < 1 || n > 65535) return "a port from 1 to 65535"; t.port = static_cast(n); rest.resize(colon); } if (!hostChars(rest)) return "that isn't a host"; t.host = rest; out = t; return ""; } SshHosts::SshHosts(const std::string& stored) { for (auto& line : linesOf(stored)) { SshTarget t; if (hosts_.size() < kMax && parseSshTarget(line, t).empty()) hosts_.push_back(t.text()); } } void SshHosts::used(const SshTarget& target) { std::string text = target.text(); hosts_.erase(std::remove(hosts_.begin(), hosts_.end(), text), hosts_.end()); hosts_.insert(hosts_.begin(), text); if (hosts_.size() > kMax) hosts_.resize(kMax); } void SshHosts::remove(size_t index) { if (index < hosts_.size()) hosts_.erase(hosts_.begin() + static_cast(index)); } std::string SshHosts::stored() const { std::string s; for (auto& h : hosts_) s += h + "\n"; return s; } SshKnownHosts::SshKnownHosts(const std::string& stored) { for (auto& line : linesOf(stored)) { size_t space = line.find(' '); if (space != std::string::npos && space > 0 && space + 1 < line.size() && known_.size() < kMax) known_.emplace_back(line.substr(0, space), line.substr(space + 1)); } } std::string SshKnownHosts::fingerprintOf(const std::string& hostPort) const { for (auto& k : known_) if (k.first == hostPort) return k.second; return ""; } void SshKnownHosts::remember(const std::string& hostPort, const std::string& fingerprint) { known_.erase(std::remove_if(known_.begin(), known_.end(), [&](const std::pair& k) { return k.first == hostPort; }), known_.end()); known_.emplace_back(hostPort, fingerprint); if (known_.size() > kMax) known_.erase(known_.begin()); } void SshKnownHosts::forget(const std::string& hostPort) { known_.erase(std::remove_if(known_.begin(), known_.end(), [&](const std::pair& k) { return k.first == hostPort; }), known_.end()); } std::string SshKnownHosts::stored() const { std::string s; for (auto& k : known_) s += k.first + " " + k.second + "\n"; return s; } } // namespace roro::term