rusthound_ce/modules/webclient/
types.rs1pub const PIPE_NAME: &str = "DAV RPC SERVICE";
13
14pub const PIPE_NAME_DISPLAY: &str = r"\PIPE\DAV RPC SERVICE";
16
17pub mod status {
19 pub const SUCCESS: u32 = 0x0000_0000;
20 pub const OBJECT_NAME_NOT_FOUND: u32 = 0xC000_0034;
21 pub const OBJECT_PATH_NOT_FOUND: u32 = 0xC000_003A;
22 pub const PIPE_NOT_AVAILABLE: u32 = 0xC000_00AC;
23 pub const ACCESS_DENIED: u32 = 0xC000_0022;
24 pub const BAD_NETWORK_NAME: u32 = 0xC000_00CC;
25 pub const LOGON_FAILURE: u32 = 0xC000_006D;
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum Outcome {
31 Running,
33 NotRunning,
35 AccessDenied(String),
37 Unreachable(String),
39 AuthFailed(String),
41 Error(String),
43}
44
45impl Outcome {
46 pub fn is_running(&self) -> bool {
47 matches!(self, Outcome::Running)
48 }
49
50 pub fn collected(&self) -> bool {
52 matches!(self, Outcome::Running | Outcome::NotRunning)
53 }
54
55 pub fn failure_reason(&self) -> Option<String> {
56 match self {
57 Outcome::Running | Outcome::NotRunning => None,
58 Outcome::AccessDenied(m)
59 | Outcome::Unreachable(m)
60 | Outcome::AuthFailed(m)
61 | Outcome::Error(m) => Some(m.clone()),
62 }
63 }
64}
65
66pub fn classify(nt_status: u32) -> Outcome {
69 match nt_status {
70 status::SUCCESS => Outcome::Running,
71 status::OBJECT_NAME_NOT_FOUND
72 | status::OBJECT_PATH_NOT_FOUND
73 | status::PIPE_NOT_AVAILABLE => Outcome::NotRunning,
74 status::ACCESS_DENIED => Outcome::AccessDenied(format!(
75 "CREATE {PIPE_NAME_DISPLAY} refused (0x{nt_status:08X})"
76 )),
77 status::BAD_NETWORK_NAME => {
78 Outcome::Error(format!("IPC$ tree connect failed (0x{nt_status:08X})"))
79 }
80 status::LOGON_FAILURE => {
81 Outcome::AuthFailed(format!("session setup rejected (0x{nt_status:08X})"))
82 }
83 other => Outcome::Error(format!("unexpected NTSTATUS 0x{other:08X}")),
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn success_is_running() {
93 assert_eq!(classify(status::SUCCESS), Outcome::Running);
94 assert!(classify(status::SUCCESS).is_running());
95 }
96
97 #[test]
98 fn missing_pipe_is_not_running() {
99 assert_eq!(classify(status::OBJECT_NAME_NOT_FOUND), Outcome::NotRunning);
100 assert_eq!(classify(status::OBJECT_PATH_NOT_FOUND), Outcome::NotRunning);
101 assert_eq!(classify(status::PIPE_NOT_AVAILABLE), Outcome::NotRunning);
102 assert!(classify(status::OBJECT_NAME_NOT_FOUND).collected());
103 }
104
105 #[test]
106 fn denied_is_uncollected() {
107 let o = classify(status::ACCESS_DENIED);
108 assert!(matches!(o, Outcome::AccessDenied(_)));
109 assert!(!o.collected());
110 assert!(!o.is_running());
111 }
112
113 #[test]
114 fn auth_and_network_map_through() {
115 assert!(matches!(classify(status::LOGON_FAILURE), Outcome::AuthFailed(_)));
116 assert!(matches!(classify(status::BAD_NETWORK_NAME), Outcome::Error(_)));
117 }
118
119 #[test]
120 fn unknown_status_is_error() {
121 assert!(matches!(classify(0xC000_0001), Outcome::Error(_)));
122 }
123}