ufwctl 0.1.0

Linux-only Rust library for managing UFW firewall rules
Documentation
use std::io;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::process::{Command, Stdio};

use crate::builders::ipv4_builder::Ipv4Builder;
use crate::builders::ipv6_builder::Ipv6Builder;
use crate::builders::port_builder::PortBuilder;
use crate::command::CommandRunner;
use crate::error::UfwError;
use crate::models::{IpRule, PortRule};
use crate::parser::{parse_ip_rules, parse_is_enabled, parse_port_rules};

/// The main entry point for interacting with UFW.
///
/// `Firewall` wraps the `ufw` command-line tool and provides a typed,
/// builder-driven API for managing firewall rules.
pub struct Firewall;

impl Default for Firewall {
    fn default() -> Self {
        Firewall
    }
}

impl Firewall {
    /// Create a new `Firewall` instance.
    pub fn new() -> Self {
        Firewall
    }

    /// Check whether the `ufw` binary is available on the system.
    pub fn is_installed(&self) -> Result<bool, UfwError> {
        match Command::new("ufw")
            .arg("--help")
            .stdout(Stdio::null())
            .stderr(Stdio::null())
            .status()
        {
            Ok(status) => Ok(status.success()),
            Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
            Err(e) => Err(UfwError::Io(e)),
        }
    }

    /// Check whether UFW is currently active.
    pub fn is_enabled(&self) -> Result<bool, UfwError> {
        let raw = CommandRunner::output("sudo", &["ufw", "status"])?;
        parse_is_enabled(&raw)
    }

    /// Enable the firewall.
    pub fn enable(&self) -> Result<(), UfwError> {
        CommandRunner::run("sudo", &["ufw", "--force", "enable"])
    }

    /// Disable the firewall.
    pub fn disable(&self) -> Result<(), UfwError> {
        CommandRunner::run("sudo", &["ufw", "disable"])
    }

    /// Reload the firewall.
    pub fn reload(&self) -> Result<(), UfwError> {
        CommandRunner::run("sudo", &["ufw", "reload"])
    }

    /// Reset the firewall to its default state.
    pub fn reset(&self) -> Result<(), UfwError> {
        CommandRunner::run("sudo", &["ufw", "--force", "reset"])
    }

    /// Set the default incoming policy to deny.
    pub fn default_deny_incoming(&self) -> Result<(), UfwError> {
        CommandRunner::run("sudo", &["ufw", "default", "deny", "incoming"])
    }

    /// Set the default outgoing policy to allow.
    pub fn default_allow_outgoing(&self) -> Result<(), UfwError> {
        CommandRunner::run("sudo", &["ufw", "default", "allow", "outgoing"])
    }

    /// Return the raw output of `ufw status`.
    pub fn raw_status(&self) -> Result<String, UfwError> {
        CommandRunner::output("sudo", &["ufw", "status"])
    }

    /// Begin building a rule for a specific port.
    ///
    /// ```no_run
    /// let fw = ufwctl::Firewall::new();
    /// let _ = fw.port(22).allow().tcp().apply();
    /// let _ = fw.port(5050).delete().both().apply();
    /// let _ = fw.port(80).status().tcp();
    /// ```
    pub fn port(&self, port: u16) -> PortBuilder<'_> {
        PortBuilder::new(self, port)
    }

    /// Begin building a rule for an IPv4 address.
    ///
    /// Returns `Err(UfwError::InvalidIpv4)` if the string is not a valid
    /// IPv4 address.
    ///
    /// ```no_run
    /// let fw = ufwctl::Firewall::new();
    /// if let Ok(b) = fw.ipv4("192.168.1.10") {
    ///     let _ = b.allow().both().apply();
    /// }
    /// if let Ok(b) = fw.ipv4("192.168.1.10") {
    ///     let _ = b.status().tcp();
    /// }
    /// ```
    pub fn ipv4(&self, ip: &str) -> Result<Ipv4Builder<'_>, UfwError> {
        let addr: Ipv4Addr = ip
            .parse()
            .map_err(|_| UfwError::InvalidIpv4(ip.to_string()))?;
        Ok(Ipv4Builder::new(self, IpAddr::V4(addr)))
    }

    /// Begin building a rule for an IPv6 address.
    ///
    /// Returns `Err(UfwError::InvalidIpv6)` if the string is not a valid
    /// IPv6 address.
    ///
    /// ```no_run
    /// let fw = ufwctl::Firewall::new();
    /// if let Ok(b) = fw.ipv6("::1") {
    ///     let _ = b.allow().both().apply();
    /// }
    /// if let Ok(b) = fw.ipv6("::1") {
    ///     let _ = b.status().tcp();
    /// }
    /// ```
    pub fn ipv6(&self, ip: &str) -> Result<Ipv6Builder<'_>, UfwError> {
        let addr: Ipv6Addr = ip
            .parse()
            .map_err(|_| UfwError::InvalidIpv6(ip.to_string()))?;
        Ok(Ipv6Builder::new(self, IpAddr::V6(addr)))
    }

    /// Return all port-based rules as parsed from `ufw status numbered`.
    pub fn ports(&self) -> Result<Vec<PortRule>, UfwError> {
        let raw = CommandRunner::output("sudo", &["ufw", "status", "numbered"])?;
        parse_port_rules(&raw)
    }

    /// Return all IP-based rules as parsed from `ufw status numbered`.
    pub fn ips(&self) -> Result<Vec<IpRule>, UfwError> {
        let raw = CommandRunner::output("sudo", &["ufw", "status", "numbered"])?;
        parse_ip_rules(&raw)
    }
}