ufwctl 0.1.0

Linux-only Rust library for managing UFW firewall rules
Documentation
use crate::Firewall;
use crate::command::{CommandRunner, combine_delete_outcomes};
use crate::enums::{Action, Protocol, RuleStatus};
use crate::error::UfwError;
use crate::parser::find_port_status;

// ---------------------------------------------------------------------------
// PortBuilder  –  entry point via Firewall::port()
// ---------------------------------------------------------------------------

pub struct PortBuilder<'a> {
    fw: &'a Firewall,
    port: u16,
}

impl<'a> PortBuilder<'a> {
    pub(crate) fn new(fw: &'a Firewall, port: u16) -> Self {
        PortBuilder { fw, port }
    }

    pub fn allow(self) -> PortActionBuilder {
        PortActionBuilder {
            port: self.port,
            action: Action::Allow,
        }
    }

    pub fn deny(self) -> PortActionBuilder {
        PortActionBuilder {
            port: self.port,
            action: Action::Deny,
        }
    }

    pub fn delete(self) -> PortDeleteBuilder {
        PortDeleteBuilder { port: self.port }
    }

    pub fn status(self) -> PortStatus<'a> {
        PortStatus {
            fw: self.fw,
            port: self.port,
        }
    }
}

// ---------------------------------------------------------------------------
// PortActionBuilder  –  allow / deny → apply() -> Result<(), UfwError>
// ---------------------------------------------------------------------------

pub struct PortActionBuilder {
    port: u16,
    action: Action,
}

impl PortActionBuilder {
    pub fn tcp(self) -> PortFinalizer {
        PortFinalizer {
            action: self.action,
            port: self.port,
            protocol: Protocol::Tcp,
        }
    }

    pub fn udp(self) -> PortFinalizer {
        PortFinalizer {
            action: self.action,
            port: self.port,
            protocol: Protocol::Udp,
        }
    }

    pub fn both(self) -> PortFinalizer {
        PortFinalizer {
            action: self.action,
            port: self.port,
            protocol: Protocol::Both,
        }
    }
}

pub struct PortFinalizer {
    port: u16,
    action: Action,
    protocol: Protocol,
}

impl PortFinalizer {
    pub fn apply(&self) -> Result<(), UfwError> {
        let protocols: &[&str] = match self.protocol {
            Protocol::Tcp => &["tcp"],
            Protocol::Udp => &["udp"],
            Protocol::Both => &["tcp", "udp"],
        };

        let action_str = match self.action {
            Action::Allow => "allow",
            Action::Deny => "deny",
        };

        for proto in protocols {
            let rule = format!("{}/{}", self.port, proto);
            CommandRunner::run("sudo", &["ufw", action_str, &rule])?;
        }
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// PortDeleteBuilder  –  delete → apply() -> Result<bool, UfwError>
// ---------------------------------------------------------------------------

pub struct PortDeleteBuilder {
    port: u16,
}

impl PortDeleteBuilder {
    pub fn tcp(self) -> PortDeleteFinalizer {
        PortDeleteFinalizer {
            port: self.port,
            protocol: Protocol::Tcp,
        }
    }

    pub fn udp(self) -> PortDeleteFinalizer {
        PortDeleteFinalizer {
            port: self.port,
            protocol: Protocol::Udp,
        }
    }

    pub fn both(self) -> PortDeleteFinalizer {
        PortDeleteFinalizer {
            port: self.port,
            protocol: Protocol::Both,
        }
    }
}

pub struct PortDeleteFinalizer {
    port: u16,
    protocol: Protocol,
}

impl PortDeleteFinalizer {
    pub fn apply(&self) -> Result<bool, UfwError> {
        let outcomes: Vec<Result<_, _>> = match self.protocol {
            Protocol::Tcp => {
                vec![
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", &format!("{}/tcp", self.port)],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", &format!("{}/tcp", self.port)],
                    ),
                ]
            }
            Protocol::Udp => {
                vec![
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", &format!("{}/udp", self.port)],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", &format!("{}/udp", self.port)],
                    ),
                ]
            }
            Protocol::Both => {
                vec![
                    // Bare port forms for rules created as `ufw allow 22` / `ufw deny 22`
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", &self.port.to_string()],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", &self.port.to_string()],
                    ),
                    // Protocol-specific forms
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", &format!("{}/tcp", self.port)],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", &format!("{}/tcp", self.port)],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", &format!("{}/udp", self.port)],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", &format!("{}/udp", self.port)],
                    ),
                ]
            }
        };
        combine_delete_outcomes(outcomes)
    }
}

// ---------------------------------------------------------------------------
// PortStatus  –  read-only status check
// ---------------------------------------------------------------------------

pub struct PortStatus<'a> {
    fw: &'a Firewall,
    port: u16,
}

impl<'a> PortStatus<'a> {
    pub fn tcp(&self) -> Result<RuleStatus, UfwError> {
        let rules = self.fw.ports()?;
        Ok(find_port_status(&rules, self.port, Protocol::Tcp))
    }

    pub fn udp(&self) -> Result<RuleStatus, UfwError> {
        let rules = self.fw.ports()?;
        Ok(find_port_status(&rules, self.port, Protocol::Udp))
    }

    pub fn both(&self) -> Result<RuleStatus, UfwError> {
        let rules = self.fw.ports()?;
        Ok(find_port_status(&rules, self.port, Protocol::Both))
    }
}