use anyhow::{Context, Result};
use std::process::Command;
use crate::config::Protocol;
pub struct UfwManager;
impl UfwManager {
pub fn new() -> Self {
Self
}
pub fn allow_port(&self, port: u16, protocol: &Protocol) -> Result<()> {
let proto_str = match protocol {
Protocol::Tcp => "tcp",
Protocol::Udp => "udp",
};
let status = Command::new("ufw")
.args(["allow", &format!("{}/{}", port, proto_str)])
.status()
.context("Failed to execute 'ufw allow'")?;
if !status.success() {
return Err(anyhow::anyhow!(
"Failed to add UFW rule for port {} {}",
port,
proto_str
));
}
tracing::info!("UFW rule added: allow {} {}", port, proto_str);
Ok(())
}
pub fn deny_port(&self, port: u16, protocol: &Protocol) -> Result<()> {
let proto_str = match protocol {
Protocol::Tcp => "tcp",
Protocol::Udp => "udp",
};
let status = Command::new("ufw")
.args(["delete", "allow", &format!("{}/{}", port, proto_str)])
.status()
.context("Failed to execute 'ufw delete allow'")?;
if !status.success() {
tracing::warn!(
"Failed to remove UFW rule for port {} {} (may not exist)",
port,
proto_str
);
} else {
tracing::info!("UFW rule removed: allow {} {}", port, proto_str);
}
Ok(())
}
pub fn is_active(&self) -> bool {
Command::new("ufw")
.args(["status"])
.output()
.map(|o| {
let stdout = String::from_utf8_lossy(&o.stdout);
stdout.contains("active")
})
.unwrap_or(false)
}
pub fn enable(&self) -> Result<()> {
let status = Command::new("ufw")
.args(["--force", "enable"])
.status()
.context("Failed to enable UFW")?;
if !status.success() {
return Err(anyhow::anyhow!("Failed to enable UFW"));
}
tracing::info!("UFW enabled");
Ok(())
}
pub fn reload(&self) -> Result<()> {
let status = Command::new("ufw")
.args(["reload"])
.status()
.context("Failed to reload UFW")?;
if !status.success() {
return Err(anyhow::anyhow!("Failed to reload UFW"));
}
tracing::info!("UFW reloaded");
Ok(())
}
}
impl Default for UfwManager {
fn default() -> Self {
Self::new()
}
}