1use std::collections::BTreeMap;
2use std::time::Duration;
3
4use crate::engine::msfrpc::MsfRpcClient;
5use crate::error::{AppError, Result};
6use crate::msgpack::MsgValue;
7
8use super::models::*;
9use super::ExploitFramework;
10
11pub struct MsfFramework {
16 client: Option<MsfRpcClient>,
17 host: String,
18 port: u16,
19 ssl: bool,
20 username: String,
21 password: String,
22 workspace: String,
23}
24
25impl MsfFramework {
26 pub fn new(host: &str, port: u16, username: &str, password: &str, ssl: bool) -> Self {
27 Self {
28 client: None,
29 host: host.to_string(),
30 port,
31 ssl,
32 username: username.to_string(),
33 password: password.to_string(),
34 workspace: "default".to_string(),
35 }
36 }
37}
38
39impl ExploitFramework for MsfFramework {
40 fn name(&self) -> &'static str {
41 "metasploit"
42 }
43
44 fn workspace(&self) -> &str {
45 &self.workspace
46 }
47
48 fn set_workspace(&mut self, workspace: &str) {
49 self.workspace = workspace.to_string();
50 }
51
52 fn connect(&mut self) -> Result<()> {
53 let mut client = MsfRpcClient::new(
54 &self.host,
55 self.port,
56 &self.username,
57 &self.password,
58 self.ssl,
59 );
60 let login = client
61 .login()
62 .map_err(|e| AppError::Network(format!("MSF-RPC login transport failure: {e}")))?;
63 if !login.success {
64 return Err(AppError::Message(format!(
65 "MSF-RPC authentication failed: {}",
66 login.error
67 )));
68 }
69 self.client = Some(client);
70 Ok(())
71 }
72
73 fn disconnect(&mut self) {
74 if let Some(client) = self.client.as_mut() {
75 client.logout();
76 }
77 self.client = None;
78 }
79
80 fn is_connected(&self) -> bool {
81 self.client
82 .as_ref()
83 .is_some_and(MsfRpcClient::is_authenticated)
84 }
85
86 fn module_exists(&mut self, module_type: &str, name: &str) -> Result<bool> {
87 let client = self.client()?;
88 client
89 .module_exists(module_type, name)
90 .map_err(|e| AppError::Network(e.to_string()))
91 }
92
93 fn module_info(&mut self, module_type: &str, name: &str) -> Result<ModuleInfo> {
94 let client = self.client()?;
95 let raw = client
96 .module_info(module_type, name)
97 .map_err(|e| AppError::Network(e.to_string()))?;
98 parse_module_info(module_type, name, &raw)
99 }
100
101 fn run_module(
102 &mut self,
103 module_type: &str,
104 name: &str,
105 options: &BTreeMap<String, String>,
106 payload: Option<&str>,
107 ) -> Result<ModuleRunResult> {
108 let client = self.client()?;
109 let raw = client
110 .module_execute(module_type, name, options, payload)
111 .map_err(|e| AppError::Network(e.to_string()))?;
112 parse_run_result(&raw)
113 }
114
115 fn monitor_job(&mut self, job_id: &str, timeout: Duration) -> Result<JobStatus> {
116 let client = self.client()?;
117 let started = std::time::Instant::now();
118 loop {
119 match client.job_list() {
120 Ok(list) => {
121 let present = job_present(&list, job_id);
122 if !present {
123 return Ok(JobStatus {
124 job_id: job_id.to_string(),
125 running: false,
126 status: "completed".to_string(),
127 error: None,
128 });
129 }
130 }
131 Err(e) => {
132 return Ok(JobStatus {
133 job_id: job_id.to_string(),
134 running: false,
135 status: "failed".to_string(),
136 error: Some(e.to_string()),
137 });
138 }
139 }
140 if started.elapsed() >= timeout {
141 return Ok(JobStatus {
142 job_id: job_id.to_string(),
143 running: true,
144 status: "still_running".to_string(),
145 error: None,
146 });
147 }
148 std::thread::sleep(Duration::from_millis(1000));
149 }
150 }
151
152 fn list_sessions(&mut self) -> Result<Vec<SessionInfo>> {
153 let client = self.client()?;
154 let raw = client
155 .session_list()
156 .map_err(|e| AppError::Network(e.to_string()))?;
157 Ok(parse_sessions(&raw))
158 }
159
160 fn list_loot(&mut self) -> Result<Vec<LootInfo>> {
161 let workspace = self.workspace.clone();
162 let client = self.client()?;
163 let raw = client
164 .db_loots(&workspace)
165 .map_err(|e| AppError::Network(e.to_string()))?;
166 Ok(parse_loot(&raw))
167 }
168}
169
170impl MsfFramework {
171 fn client(&mut self) -> Result<&mut MsfRpcClient> {
172 self.client.as_mut().ok_or_else(|| {
173 AppError::Message("MSF-RPC not connected; call connect() first".to_string())
174 })
175 }
176}
177
178fn parse_module_info(module_type: &str, name: &str, raw: &MsgValue) -> Result<ModuleInfo> {
179 if raw.get("error").and_then(MsgValue::as_str) == Some("success") {
180 return Err(AppError::Message(format!(
181 "module not found on the connected instance: {module_type}/{name}"
182 )));
183 }
184 let rank = raw
185 .get("rank")
186 .and_then(MsgValue::as_str)
187 .unwrap_or("")
188 .to_string();
189 let description = raw
190 .get("description")
191 .and_then(MsgValue::as_str)
192 .unwrap_or("")
193 .to_string();
194
195 let mut platforms = parse_strings(raw, "platforms");
196 if platforms.is_empty() {
197 if let Some(single) = raw.get("platform").and_then(MsgValue::as_str) {
198 platforms.push(single.to_string());
199 }
200 }
201 let targets = parse_strings(raw, "targets");
202
203 let options = parse_options(raw);
204 let mut required_options = parse_strings(raw, "required_options");
205 if required_options.is_empty() {
206 required_options = options
207 .iter()
208 .filter(|opt| opt.required && opt.default.is_none())
209 .map(|opt| opt.name.clone())
210 .collect();
211 }
212
213 Ok(ModuleInfo {
214 name: name.to_string(),
215 module_type: module_type.to_string(),
216 rank,
217 description,
218 platforms,
219 targets,
220 options,
221 required_options,
222 })
223}
224
225fn parse_options(raw: &MsgValue) -> Vec<ModuleOption> {
226 let mut options = Vec::new();
227 if let Some(map) = raw.get("options").and_then(MsgValue::as_map) {
228 for (name, opt_val) in map {
229 let required = opt_val.get("required").is_some_and(|v| match v {
230 MsgValue::Bool(b) => *b,
231 MsgValue::String(s) => s == "true",
232 MsgValue::UInt(n) => *n != 0,
233 MsgValue::Int(n) => *n != 0,
234 _ => false,
235 });
236 let default = opt_val
237 .get("default")
238 .and_then(MsgValue::as_str)
239 .map(str::to_string);
240 let description = opt_val
241 .get("desc")
242 .and_then(MsgValue::as_str)
243 .unwrap_or("")
244 .to_string();
245 options.push(ModuleOption {
246 name: name.clone(),
247 required,
248 default,
249 description,
250 });
251 }
252 }
253 options
254}
255
256fn parse_run_result(raw: &MsgValue) -> Result<ModuleRunResult> {
257 let raw_text = format!("{raw:?}");
258
259 let error_flag = match raw.get("error") {
260 Some(MsgValue::Bool(b)) => *b,
261 Some(MsgValue::String(s)) => s == "true",
262 Some(MsgValue::UInt(n)) => *n != 0,
263 _ => false,
264 };
265 if error_flag {
266 let message = raw
267 .get("error_message")
268 .or_else(|| raw.get("error_string"))
269 .and_then(MsgValue::as_str)
270 .unwrap_or("module execution failed")
271 .to_string();
272 return Ok(ModuleRunResult {
273 job_id: None,
274 success: false,
275 message,
276 raw: raw_text,
277 });
278 }
279
280 let job_id = raw
281 .get("job_id")
282 .and_then(MsgValue::as_u64)
283 .map(|id| id.to_string());
284 let message = raw
285 .get("message")
286 .and_then(MsgValue::as_str)
287 .unwrap_or("module started")
288 .to_string();
289 Ok(ModuleRunResult {
290 job_id,
291 success: true,
292 message,
293 raw: raw_text,
294 })
295}
296
297fn job_present(list: &MsgValue, job_id: &str) -> bool {
298 if let Some(map) = list.get("jobs").and_then(MsgValue::as_map) {
299 map.keys().any(|id| id == job_id)
300 } else if let Some(map) = list.as_map() {
301 map.get("jobs").is_none() && map.keys().any(|id| id == job_id)
302 } else {
303 false
304 }
305}
306
307fn parse_sessions(raw: &MsgValue) -> Vec<SessionInfo> {
308 let mut sessions = Vec::new();
309 if let Some(map) = raw.get("sessions").and_then(MsgValue::as_map) {
310 for (id, val) in map {
311 let typ = val
312 .get("type")
313 .or_else(|| val.get("session_type"))
314 .and_then(MsgValue::as_str)
315 .unwrap_or("")
316 .to_string();
317 let target_host = val
318 .get("target_host")
319 .or_else(|| val.get("host"))
320 .and_then(MsgValue::as_str)
321 .unwrap_or("")
322 .to_string();
323 let platform = val
324 .get("platform")
325 .and_then(MsgValue::as_str)
326 .unwrap_or("")
327 .to_string();
328 let info = val
329 .get("info")
330 .and_then(MsgValue::as_str)
331 .unwrap_or("")
332 .to_string();
333 sessions.push(SessionInfo {
334 id: id.clone(),
335 typ,
336 target_host,
337 platform,
338 info,
339 });
340 }
341 }
342 sessions
343}
344
345fn parse_loot(raw: &MsgValue) -> Vec<LootInfo> {
346 let mut loot = Vec::new();
347 if let Some(array) = raw.get("loot").and_then(MsgValue::as_array) {
348 for val in array {
349 let id = val
350 .get("loot_id")
351 .or_else(|| val.get("id"))
352 .and_then(MsgValue::as_str)
353 .unwrap_or("")
354 .to_string();
355 let name = val
356 .get("name")
357 .and_then(MsgValue::as_str)
358 .unwrap_or("")
359 .to_string();
360 let content_type = val
361 .get("content_type")
362 .and_then(MsgValue::as_str)
363 .unwrap_or("")
364 .to_string();
365 let host = val
366 .get("host")
367 .and_then(MsgValue::as_str)
368 .unwrap_or("")
369 .to_string();
370 let path = val
371 .get("path")
372 .and_then(MsgValue::as_str)
373 .unwrap_or("")
374 .to_string();
375 loot.push(LootInfo {
376 id,
377 name,
378 content_type,
379 host,
380 path,
381 });
382 }
383 }
384 loot
385}
386
387fn parse_strings(raw: &MsgValue, key: &str) -> Vec<String> {
388 match raw.get(key) {
389 Some(MsgValue::Array(arr)) => arr
390 .iter()
391 .filter_map(MsgValue::as_str)
392 .map(str::to_string)
393 .collect(),
394 _ => Vec::new(),
395 }
396}