nessus_parser/
ping.rs

1use crate::{MacAddress, error::FormatError};
2
3#[derive(Debug, Clone, Copy)]
4pub enum PingOutcome {
5    // The remote host (<ip>) is considered as dead - not scanning
6    // The remote host (<ip>) did not respond to the following ping methods :
7    // - TCP ping
8    // - ICMP ping
9    HostDidntRespondToPingMethods { tcp: bool, icmp: bool },
10    // The remote host replied to an ICMP echo packet
11    IcmpEchoPacket,
12    // The remote host (<ip>) is considered as dead - not scanning
13    // The remote host ('<ip>') is on the local network and failed to reply to an ARP who-is query.
14    HostLocalNetworkDidntRespondArpWhoIsQuery,
15    ArpWhoIsQuery(MacAddress),
16    // The remote host replied with an ICMP unreach packet sent in response to a TCP SYN packet sent to port <u16>
17    IcmpUnreachPacketInResponseToTcpSynPacket { to: u16 },
18    // The remote host replied to a TCP SYN packet sent to port <u16> with a RST,ACK packet
19    RepliedTcpSynPacketWithRstAck { to: u16 },
20    // The remote host replied to a TCP SYN packet sent to port <u16> with a SYN,ACK packet
21    RepliedTcpSynPacketWithSynAck { to: u16 },
22    // The remote host replied to a TCP SYN packet sent to port <u16> with a FIN,ACK packet
23    RepliedTcpSynPacketWithFinAck { to: u16 },
24    // The remote host emitted a TCP SYN packet from port <u16> going to port <u16>
25    EmittedTcpSynPacket { from: u16, to: u16 },
26    // The remote host emitted a TCP ACK packet from port <u16> going to port <u16>
27    EmittedTcpAckPacket { from: u16, to: u16 },
28    // The remote host emitted a TCP PUSH,ACK packet from port <u16> going to port <u16>
29    EmittedTcpPushAckPacket { from: u16, to: u16 },
30    // The remote host replied with an ICMP unreach packet.
31    IcmpUnreachPacket,
32    // The remote host emited a UDP packet from port <u16> going to port <u16>
33    EmittedUdpPacket { from: u16, to: u16 },
34    // The host is the local scanner.
35    LocalScanner,
36}
37
38impl PingOutcome {
39    pub(crate) fn from_plugin_output(plugin_output: &str) -> Result<Self, FormatError> {
40        const LOCAL_SCANNER: &str = "The remote host is up\nThe host is the local scanner.";
41        const ICMP_ECHO_PACKET: &str =
42            "The remote host is up\nThe remote host replied to an ICMP echo packet";
43        const ICMP_UNREACH_PACKET: &str =
44            "The remote host is up\nThe remote host replied with an ICMP unreach packet.";
45        const ARP_WHO_IS_QUERY_PREFIX: &str =
46            "The remote host is up\nThe host replied to an ARP who-is query.\nHardware address : ";
47        const REPLIED_TCP_SYN_PACKET_PREFIX: &str =
48            "The remote host is up\nThe remote host replied to a TCP SYN packet sent to port ";
49        const WITH_RST_ACK_SUFFIX: &str = " with a RST,ACK packet";
50        const WITH_SYN_ACK_SUFFIX: &str = " with a SYN,ACK packet";
51        const WITH_FIN_ACK_SUFFIX: &str = " with a FIN,ACK packet";
52        const ICMP_UNREACH_PACKET_RESPONSE_TO_TCP_SYN_PACKET_PREFIX: &str = "The remote host is up\nThe remote host replied with an ICMP unreach packet sent in response to a TCP SYN packet sent to port ";
53        const EMITTED_UDP_PACKET_PREFIX: &str =
54            "The remote host is up\nThe remote host emited a UDP packet from port ";
55        const EMITTED_PACKET_MIDDLE: &str = "going to port ";
56        const EMITTED_TCP_SYN_PACKET_PREFIX: &str =
57            "The remote host is up\nThe remote host emitted a TCP SYN packet from port ";
58        const EMITTED_TCP_ACK_PACKET_PREFIX: &str =
59            "The remote host is up\nThe remote host emitted a TCP ACK packet from port ";
60        const EMITTED_TCP_PUSH_ACK_PACKET_PREFIX: &str =
61            "The remote host is up\nThe remote host emitted a TCP PUSH,ACK packet from port ";
62        const DEAD_HOST_NEEDLE: &str = ") is considered as dead - not scanning\nThe remote host (";
63        const FAILED_TO_REPLY_ARP_SUFFIX: &str =
64            ") is on the local network and failed to reply to an ARP who-is query.";
65        const DIDNT_RESPOND_TO_PING_METHODS_NEEDLE: &str =
66            ") did not respond to the following ping methods :\n";
67
68        match plugin_output {
69            ICMP_ECHO_PACKET => Ok(Self::IcmpEchoPacket),
70            LOCAL_SCANNER => Ok(Self::LocalScanner),
71            ICMP_UNREACH_PACKET => Ok(Self::IcmpUnreachPacket),
72            text => {
73                if text.contains(DEAD_HOST_NEEDLE) {
74                    if text.ends_with(FAILED_TO_REPLY_ARP_SUFFIX) {
75                        Ok(Self::HostLocalNetworkDidntRespondArpWhoIsQuery)
76                    } else if let Some((_, ping_methods)) =
77                        text.split_once(DIDNT_RESPOND_TO_PING_METHODS_NEEDLE)
78                    {
79                        let mut tcp = false;
80                        let mut icmp = false;
81                        for ping_method in ping_methods.trim_end().split('\n') {
82                            match ping_method {
83                                "- TCP ping" => tcp = true,
84                                "- ICMP ping" => icmp = true,
85                                _ => {
86                                    return Err(FormatError::UnexpectedPingMethod(
87                                        plugin_output.into(),
88                                    ));
89                                }
90                            }
91                        }
92                        if !tcp && !icmp {
93                            Err(FormatError::UnexpectedPingFormat(plugin_output.into()))
94                        } else {
95                            Ok(Self::HostDidntRespondToPingMethods { tcp, icmp })
96                        }
97                    } else {
98                        Err(FormatError::UnexpectedDeadHostReason(plugin_output.into()))
99                    }
100                } else if let Some(mac) = text.strip_prefix(ARP_WHO_IS_QUERY_PREFIX) {
101                    Ok(Self::ArpWhoIsQuery(mac.parse()?))
102                } else if let Some(port) =
103                    text.strip_prefix(ICMP_UNREACH_PACKET_RESPONSE_TO_TCP_SYN_PACKET_PREFIX)
104                {
105                    Ok(Self::IcmpUnreachPacketInResponseToTcpSynPacket { to: port.parse()? })
106                } else if let Some(port_and_suffix) =
107                    text.strip_prefix(REPLIED_TCP_SYN_PACKET_PREFIX)
108                {
109                    if let Some(port) = port_and_suffix.strip_suffix(WITH_RST_ACK_SUFFIX) {
110                        Ok(Self::RepliedTcpSynPacketWithRstAck { to: port.parse()? })
111                    } else if let Some(port) = port_and_suffix.strip_suffix(WITH_SYN_ACK_SUFFIX) {
112                        Ok(Self::RepliedTcpSynPacketWithSynAck { to: port.parse()? })
113                    } else if let Some(port) = port_and_suffix.strip_suffix(WITH_FIN_ACK_SUFFIX) {
114                        Ok(Self::RepliedTcpSynPacketWithFinAck { to: port.parse()? })
115                    } else {
116                        Err(FormatError::UnexpectedPingTcpResponse(plugin_output.into()))
117                    }
118                } else if let Some(from_port_and_rest) =
119                    text.strip_prefix(EMITTED_TCP_SYN_PACKET_PREFIX)
120                    && let Some((from_port, rest)) = from_port_and_rest.split_once(' ')
121                    && let Some(to_port) = rest.strip_prefix(EMITTED_PACKET_MIDDLE)
122                {
123                    Ok(Self::EmittedTcpSynPacket {
124                        from: from_port.parse()?,
125                        to: to_port.parse()?,
126                    })
127                // TODO: DRY
128                } else if let Some(from_port_and_rest) =
129                    text.strip_prefix(EMITTED_TCP_ACK_PACKET_PREFIX)
130                    && let Some((from_port, rest)) = from_port_and_rest.split_once(' ')
131                    && let Some(to_port) = rest.strip_prefix(EMITTED_PACKET_MIDDLE)
132                {
133                    Ok(Self::EmittedTcpAckPacket {
134                        from: from_port.parse()?,
135                        to: to_port.parse()?,
136                    })
137                } else if let Some(from_port_and_rest) =
138                    text.strip_prefix(EMITTED_TCP_PUSH_ACK_PACKET_PREFIX)
139                    && let Some((from_port, rest)) = from_port_and_rest.split_once(' ')
140                    && let Some(to_port) = rest.strip_prefix(EMITTED_PACKET_MIDDLE)
141                {
142                    Ok(Self::EmittedTcpAckPacket {
143                        from: from_port.parse()?,
144                        to: to_port.parse()?,
145                    })
146                } else if let Some(from_port_and_rest) =
147                    text.strip_prefix(EMITTED_UDP_PACKET_PREFIX)
148                    && let Some((from_port, rest)) = from_port_and_rest.split_once(' ')
149                    && let Some(to_port) = rest.strip_prefix(EMITTED_PACKET_MIDDLE)
150                {
151                    Ok(Self::EmittedUdpPacket {
152                        from: from_port.parse()?,
153                        to: to_port.parse()?,
154                    })
155                } else {
156                    Err(FormatError::UnexpectedPingFormat(plugin_output.into()))
157                }
158            }
159        }
160    }
161}