wireforge_app/ssh/
banner.rs1use std::str;
4
5#[derive(Debug, Clone)]
6pub struct SshBanner<'a> { buf: &'a [u8], proto_end: usize, sw_end: usize }
7
8impl<'a> SshBanner<'a> {
9 pub fn parse(buf: &'a [u8]) -> Option<Self> {
10 let text = str::from_utf8(buf).ok()?;
11 if !text.starts_with("SSH-") { return None; }
12 let line = if let Some(pos) = text.find("\r\n") { &text[..pos] } else { text };
13 let proto = &line[4..]; let dash = proto.find('-')?;
15 let proto_end = 4 + dash;
16 let sw_end = line.len();
17 Some(Self { buf, proto_end, sw_end })
18 }
19
20 pub fn protocol_version(&self) -> &str {
21 str::from_utf8(&self.buf[4..self.proto_end]).unwrap_or("")
22 }
23
24 pub fn software(&self) -> &str {
25 str::from_utf8(&self.buf[self.proto_end + 1..self.sw_end]).unwrap_or("")
26 }
27
28 pub fn raw(&self) -> &str {
29 str::from_utf8(&self.buf[..self.sw_end]).unwrap_or("")
30 }
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[test]
38 fn parse_openssh_banner() {
39 let data = b"SSH-2.0-OpenSSH_8.9\r\n";
40 let b = SshBanner::parse(data).unwrap();
41 assert_eq!(b.protocol_version(), "2.0");
42 assert_eq!(b.software(), "OpenSSH_8.9");
43 }
44
45 #[test]
46 fn parse_dropbear_banner() {
47 let data = b"SSH-2.0-dropbear_2022.83\r\n";
48 let b = SshBanner::parse(data).unwrap();
49 assert_eq!(b.software(), "dropbear_2022.83");
50 }
51
52 #[test]
53 fn parse_not_ssh() {
54 assert!(SshBanner::parse(b"GET / HTTP/1.1\r\n").is_none());
55 }
56}