use aurora_core::{AuroraResult, Pipeline, Value};
use std::process::Command;
fn check_tool(name: &str) -> AuroraResult<()> {
let status = Command::new("which")
.arg(name)
.output()
.ok()
.map(|o| o.status.success())
.unwrap_or(false);
if !status {
return Err(aurora_core::AuroraError::CommandNotFound(
format!("{} is not installed", name)
));
}
Ok(())
}
pub fn net_ping(host: &str) -> AuroraResult<Pipeline> {
check_tool("ping")?;
let output = Command::new("ping")
.args(["-c", "4", host])
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("ping failed: {}", e)
))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let combined = format!("{}{}", stdout, stderr);
let sent = 4i64;
let received = combined.lines()
.filter(|l| l.contains("bytes from") || l.contains("icmp_seq"))
.count() as i64;
let mut min_ms = String::new();
let mut avg_ms = String::new();
let mut max_ms = String::new();
for line in combined.lines() {
if line.contains("rtt min/avg/max") || line.contains("round-trip") {
let parts: Vec<&str> = line.split('=').collect();
if parts.len() >= 2 {
let stats = parts[1].trim();
let nums: Vec<&str> = stats.split('/').collect();
if nums.len() >= 3 {
min_ms = nums[0].trim().to_string();
avg_ms = nums[1].trim().to_string();
max_ms = nums[2].trim().to_string();
}
}
}
}
Ok(Pipeline::table(
vec!["sent".into(), "received".into(), "min_ms".into(), "avg_ms".into(), "max_ms".into()],
vec![vec![
Value::Int(sent),
Value::Int(received),
Value::String(min_ms),
Value::String(avg_ms),
Value::String(max_ms),
]],
))
}
pub fn net_dns(domain: &str) -> AuroraResult<Pipeline> {
if find_tool("dig") {
let output = Command::new("dig")
.args([domain, "+short"])
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("dig failed: {}", e)
))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let rows: Vec<Vec<Value>> = stdout.lines()
.filter(|l| !l.is_empty())
.map(|l| vec![Value::String(l.into())])
.collect();
return Ok(Pipeline::table(vec!["record".into()], rows));
}
if find_tool("nslookup") {
let output = Command::new("nslookup")
.arg(domain)
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("nslookup failed: {}", e)
))?;
let stdout = String::from_utf8_lossy(&output.stdout);
return Ok(Pipeline::table(
vec!["record".into()],
vec![vec![Value::String(stdout.trim().into())]],
));
}
Err(aurora_core::AuroraError::CommandNotFound(
"neither dig nor nslookup are installed".into()
))
}
fn find_tool(name: &str) -> bool {
Command::new("which")
.arg(name)
.output()
.ok()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn net_http(url: &str) -> AuroraResult<Pipeline> {
check_tool("curl")?;
let output = Command::new("curl")
.args(["-sI", url])
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("curl failed: {}", e)
))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut rows: Vec<Vec<Value>> = Vec::new();
let mut status = String::new();
for line in stdout.lines() {
if line.is_empty() { continue; }
if line.starts_with("HTTP/") {
status = line.to_string();
} else {
if let Some(idx) = line.find(':') {
let key = &line[..idx];
let val = &line[idx + 1..];
rows.push(vec![
Value::String(key.trim().into()),
Value::String(val.trim().into()),
]);
}
}
}
if !status.is_empty() {
rows.insert(0, vec![
Value::String("Status".into()),
Value::String(status),
]);
}
Ok(Pipeline::table(
vec!["header".into(), "value".into()],
rows,
))
}
pub fn net_ip() -> AuroraResult<Pipeline> {
if find_tool("curl") {
let output = Command::new("curl")
.args(["-s", "https://api.ipify.org?format=json"])
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("curl failed: {}", e)
))?;
let stdout = String::from_utf8_lossy(&output.stdout);
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&stdout) {
if let Some(ip) = parsed.get("ip").and_then(|v| v.as_str()) {
return Ok(Pipeline::table(
vec!["ip".into()],
vec![vec![Value::String(ip.into())]],
));
}
}
}
let output = Command::new("hostname")
.args(["-I"])
.output()
.ok();
if let Some(out) = output {
let stdout = String::from_utf8_lossy(&out.stdout);
let ip = stdout.trim().split(' ').next().unwrap_or("").to_string();
if !ip.is_empty() {
return Ok(Pipeline::table(
vec!["ip".into()],
vec![vec![Value::String(ip)]],
));
}
}
Ok(Pipeline::table(
vec!["ip".into()],
vec![vec![Value::String("unknown".into())]],
))
}
pub fn net_scan(host: &str) -> AuroraResult<Pipeline> {
if find_tool("nmap") {
let output = Command::new("nmap")
.args(["-F", host])
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("nmap failed: {}", e)
))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut rows: Vec<Vec<Value>> = Vec::new();
for line in stdout.lines() {
let line = line.trim();
if line.contains("/tcp") || line.contains("/udp") {
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(port_part) = parts.first() {
rows.push(vec![
Value::String(port_part.to_string()),
Value::String(parts.get(1).copied().unwrap_or("").into()),
Value::String(parts.get(2).copied().unwrap_or("").into()),
]);
}
}
}
return Ok(Pipeline::table(
vec!["port".into(), "state".into(), "service".into()],
rows,
));
}
Err(aurora_core::AuroraError::CommandNotFound(
"nmap is not installed".into()
))
}