network.rs (15078B)
1 use crate::chatops::{ChatCommand, ChatOpError, ChatOpResult, CommandContext}; 2 use std::process::Command; 3 4 /// Ping command 5 pub struct PingCommand; 6 7 impl ChatCommand for PingCommand { 8 fn name(&self) -> &'static str { 9 "ping" 10 } 11 fn description(&self) -> &'static str { 12 "Ping a host" 13 } 14 fn usage(&self) -> &'static str { 15 "/ping <host>" 16 } 17 18 fn execute( 19 &self, 20 args: Vec<String>, 21 _context: &CommandContext, 22 ) -> Result<ChatOpResult, ChatOpError> { 23 if args.is_empty() { 24 return Err(ChatOpError::MissingArguments( 25 "Please specify a host to ping".to_string(), 26 )); 27 } 28 29 let host = &args[0]; 30 31 match Command::new("ping") 32 .args(&["-c", "3", host]) // 3 packets on Unix 33 .output() 34 { 35 Ok(output) => { 36 if output.status.success() { 37 let result = String::from_utf8_lossy(&output.stdout); 38 let lines: Vec<String> = result 39 .lines() 40 .filter(|line| { 41 line.contains("time=") 42 || line.contains("packet loss") 43 || line.contains("min/avg/max") 44 }) 45 .take(5) 46 .map(|line| format!("🏓 {}", line.trim())) 47 .collect(); 48 49 if lines.is_empty() { 50 Ok(ChatOpResult::Message(format!( 51 "🏓 Ping to {} completed", 52 host 53 ))) 54 } else { 55 Ok(ChatOpResult::Block(lines)) 56 } 57 } else { 58 Ok(ChatOpResult::Message(format!("🏓 Ping to {} failed", host))) 59 } 60 } 61 Err(_) => Ok(ChatOpResult::Message(format!( 62 "🏓 Ping command not available" 63 ))), 64 } 65 } 66 } 67 68 /// Traceroute command 69 pub struct TraceCommand; 70 71 impl ChatCommand for TraceCommand { 72 fn name(&self) -> &'static str { 73 "trace" 74 } 75 fn description(&self) -> &'static str { 76 "Trace route to host" 77 } 78 fn usage(&self) -> &'static str { 79 "/trace <host>" 80 } 81 fn aliases(&self) -> Vec<&'static str> { 82 vec!["traceroute"] 83 } 84 85 fn execute( 86 &self, 87 args: Vec<String>, 88 _context: &CommandContext, 89 ) -> Result<ChatOpResult, ChatOpError> { 90 if args.is_empty() { 91 return Err(ChatOpError::MissingArguments( 92 "Please specify a host to trace".to_string(), 93 )); 94 } 95 96 let host = &args[0]; 97 98 match Command::new("traceroute") 99 .args(&["-m", "10", host]) // Max 10 hops 100 .output() 101 { 102 Ok(output) => { 103 if output.status.success() { 104 let result = String::from_utf8_lossy(&output.stdout); 105 let lines: Vec<String> = result 106 .lines() 107 .take(12) // Limit output 108 .map(|line| format!("📍 {}", line.trim())) 109 .collect(); 110 111 if lines.is_empty() { 112 Ok(ChatOpResult::Message(format!( 113 "📍 Traceroute to {} completed", 114 host 115 ))) 116 } else { 117 Ok(ChatOpResult::Block(lines)) 118 } 119 } else { 120 Ok(ChatOpResult::Message(format!( 121 "📍 Traceroute to {} failed", 122 host 123 ))) 124 } 125 } 126 Err(_) => { 127 // Try with tracepath as alternative 128 match Command::new("tracepath").arg(host).output() { 129 Ok(output) if output.status.success() => { 130 let result = String::from_utf8_lossy(&output.stdout); 131 let lines: Vec<String> = result 132 .lines() 133 .take(12) 134 .map(|line| format!("📍 {}", line.trim())) 135 .collect(); 136 Ok(ChatOpResult::Block(lines)) 137 } 138 _ => Ok(ChatOpResult::Message( 139 "📍 Traceroute command not available".to_string(), 140 )), 141 } 142 } 143 } 144 } 145 } 146 147 /// Port scan command 148 pub struct PortScanCommand; 149 150 impl ChatCommand for PortScanCommand { 151 fn name(&self) -> &'static str { 152 "portscan" 153 } 154 fn description(&self) -> &'static str { 155 "Simple port scan (TCP connect)" 156 } 157 fn usage(&self) -> &'static str { 158 "/portscan <host> [ports]" 159 } 160 161 fn execute( 162 &self, 163 args: Vec<String>, 164 _context: &CommandContext, 165 ) -> Result<ChatOpResult, ChatOpError> { 166 if args.is_empty() { 167 return Err(ChatOpError::MissingArguments( 168 "Please specify a host to scan".to_string(), 169 )); 170 } 171 172 let host = &args[0]; 173 let ports = args.get(1).unwrap_or(&"22,80,443".to_string()).clone(); 174 175 // Simple TCP connect scan using netcat if available 176 match Command::new("nc") 177 .args(&["-z", "-v", "-w", "2", host, &ports]) 178 .output() 179 { 180 Ok(output) => { 181 let stdout = String::from_utf8_lossy(&output.stdout); 182 let stderr = String::from_utf8_lossy(&output.stderr); 183 let result = format!("{}{}", stdout, stderr); 184 185 let lines: Vec<String> = result 186 .lines() 187 .filter(|line| !line.trim().is_empty()) 188 .take(10) 189 .map(|line| format!("🔍 {}", line.trim())) 190 .collect(); 191 192 if lines.is_empty() { 193 Ok(ChatOpResult::Message(format!( 194 "🔍 Port scan of {} completed (no open ports found)", 195 host 196 ))) 197 } else { 198 Ok(ChatOpResult::Block(lines)) 199 } 200 } 201 Err(_) => { 202 // Try with nmap if available 203 match Command::new("nmap").args(&["-p", &ports, host]).output() { 204 Ok(output) if output.status.success() => { 205 let result = String::from_utf8_lossy(&output.stdout); 206 let lines: Vec<String> = result 207 .lines() 208 .filter(|line| { 209 line.contains("/tcp") || line.contains("Nmap scan report") 210 }) 211 .take(10) 212 .map(|line| format!("🔍 {}", line.trim())) 213 .collect(); 214 Ok(ChatOpResult::Block(lines)) 215 } 216 _ => Ok(ChatOpResult::Message( 217 "🔍 Port scanning tools not available (nc/nmap)".to_string(), 218 )), 219 } 220 } 221 } 222 } 223 } 224 225 /// HTTP headers command 226 pub struct HeadersCommand; 227 228 impl ChatCommand for HeadersCommand { 229 fn name(&self) -> &'static str { 230 "headers" 231 } 232 fn description(&self) -> &'static str { 233 "Show HTTP response headers" 234 } 235 fn usage(&self) -> &'static str { 236 "/headers <url>" 237 } 238 239 fn execute( 240 &self, 241 args: Vec<String>, 242 _context: &CommandContext, 243 ) -> Result<ChatOpResult, ChatOpError> { 244 if args.is_empty() { 245 return Err(ChatOpError::MissingArguments( 246 "Please specify a URL".to_string(), 247 )); 248 } 249 250 let url = &args[0]; 251 252 match Command::new("curl").args(&["-I", "-s", url]).output() { 253 Ok(output) => { 254 if output.status.success() { 255 let result = String::from_utf8_lossy(&output.stdout); 256 let lines: Vec<String> = result 257 .lines() 258 .filter(|line| !line.trim().is_empty()) 259 .take(15) // Limit headers 260 .map(|line| format!("📡 {}", line.trim())) 261 .collect(); 262 263 if lines.is_empty() { 264 Ok(ChatOpResult::Message(format!( 265 "📡 No headers received from {}", 266 url 267 ))) 268 } else { 269 Ok(ChatOpResult::Block(lines)) 270 } 271 } else { 272 Ok(ChatOpResult::Message(format!( 273 "📡 Failed to fetch headers from {}", 274 url 275 ))) 276 } 277 } 278 Err(_) => Ok(ChatOpResult::Message( 279 "📡 curl command not available".to_string(), 280 )), 281 } 282 } 283 } 284 285 /// Simple HTTP request command 286 pub struct CurlCommand; 287 288 impl ChatCommand for CurlCommand { 289 fn name(&self) -> &'static str { 290 "curl" 291 } 292 fn description(&self) -> &'static str { 293 "Make HTTP request and show response" 294 } 295 fn usage(&self) -> &'static str { 296 "/curl <url>" 297 } 298 299 fn execute( 300 &self, 301 args: Vec<String>, 302 _context: &CommandContext, 303 ) -> Result<ChatOpResult, ChatOpError> { 304 if args.is_empty() { 305 return Err(ChatOpError::MissingArguments( 306 "Please specify a URL".to_string(), 307 )); 308 } 309 310 let url = &args[0]; 311 312 match Command::new("curl") 313 .args(&["-s", "-L", "--max-time", "10", url]) 314 .output() 315 { 316 Ok(output) => { 317 if output.status.success() { 318 let result = String::from_utf8_lossy(&output.stdout); 319 let lines: Vec<String> = result 320 .lines() 321 .take(20) // Limit response body 322 .map(|line| line.to_string()) 323 .collect(); 324 325 if lines.is_empty() { 326 Ok(ChatOpResult::Message(format!( 327 "🌐 Empty response from {}", 328 url 329 ))) 330 } else { 331 Ok(ChatOpResult::CodeBlock( 332 lines.join("\n"), 333 Some("text".to_string()), 334 )) 335 } 336 } else { 337 Ok(ChatOpResult::Message(format!("🌐 Failed to fetch {}", url))) 338 } 339 } 340 Err(_) => Ok(ChatOpResult::Message( 341 "🌐 curl command not available".to_string(), 342 )), 343 } 344 } 345 } 346 347 /// SSL certificate information 348 pub struct SslCommand; 349 350 impl ChatCommand for SslCommand { 351 fn name(&self) -> &'static str { 352 "ssl" 353 } 354 fn description(&self) -> &'static str { 355 "Show SSL certificate information" 356 } 357 fn usage(&self) -> &'static str { 358 "/ssl <domain>" 359 } 360 fn aliases(&self) -> Vec<&'static str> { 361 vec!["cert"] 362 } 363 364 fn execute( 365 &self, 366 args: Vec<String>, 367 _context: &CommandContext, 368 ) -> Result<ChatOpResult, ChatOpError> { 369 if args.is_empty() { 370 return Err(ChatOpError::MissingArguments( 371 "Please specify a domain".to_string(), 372 )); 373 } 374 375 let domain = &args[0]; 376 377 match Command::new("openssl") 378 .args(&["s_client", "-connect", &format!("{}:443", domain), "-servername", domain]) 379 .stdin(std::process::Stdio::null()) 380 .output() 381 { 382 Ok(output) => { 383 let result = String::from_utf8_lossy(&output.stdout); 384 let lines: Vec<String> = result 385 .lines() 386 .filter(|line| { 387 line.contains("subject=") || 388 line.contains("issuer=") || 389 line.contains("notBefore=") || 390 line.contains("notAfter=") || 391 line.contains("Verification:") 392 }) 393 .take(8) 394 .map(|line| format!("🔒 {}", line.trim())) 395 .collect(); 396 397 if lines.is_empty() { 398 Ok(ChatOpResult::Message(format!("🔒 Could not retrieve SSL certificate for {}", domain))) 399 } else { 400 Ok(ChatOpResult::Block(lines)) 401 } 402 } 403 Err(_) => Ok(ChatOpResult::Message(format!("🔒 SSL check not available. Try: https://www.ssllabs.com/ssltest/analyze.html?d={}", domain))), 404 } 405 } 406 } 407 408 /// Tor exit node checker 409 pub struct TorCheckCommand; 410 411 impl ChatCommand for TorCheckCommand { 412 fn name(&self) -> &'static str { 413 "torcheck" 414 } 415 fn description(&self) -> &'static str { 416 "Check if IP is a Tor exit node" 417 } 418 fn usage(&self) -> &'static str { 419 "/torcheck <ip>" 420 } 421 422 fn execute( 423 &self, 424 args: Vec<String>, 425 _context: &CommandContext, 426 ) -> Result<ChatOpResult, ChatOpError> { 427 if args.is_empty() { 428 return Err(ChatOpError::MissingArguments( 429 "Please specify an IP address".to_string(), 430 )); 431 } 432 433 let ip = &args[0]; 434 435 // Basic IP validation 436 if !ip.chars().all(|c| c.is_ascii_digit() || c == '.') || ip.split('.').count() != 4 { 437 return Err(ChatOpError::InvalidSyntax( 438 "Please provide a valid IPv4 address".to_string(), 439 )); 440 } 441 442 // Try checking with a Tor exit list service 443 match Command::new("curl") 444 .args(&["-s", "--max-time", "5", &format!("https://check.torproject.org/torbulkexitlist?ip={}", ip)]) 445 .output() 446 { 447 Ok(output) => { 448 if output.status.success() { 449 let result = String::from_utf8_lossy(&output.stdout); 450 if result.contains(ip) { 451 Ok(ChatOpResult::Message(format!("🧅 {} is a Tor exit node", ip))) 452 } else { 453 Ok(ChatOpResult::Message(format!("🧅 {} is not a known Tor exit node", ip))) 454 } 455 } else { 456 Ok(ChatOpResult::Message(format!("🧅 Could not check Tor status for {}", ip))) 457 } 458 } 459 Err(_) => Ok(ChatOpResult::Message(format!("🧅 Tor check not available. Try: https://metrics.torproject.org/exonerator.html?ip={}", ip))), 460 } 461 } 462 }