ufwctl 0.1.0

Linux-only Rust library for managing UFW firewall rules
Documentation
use std::net::IpAddr;

use crate::Firewall;
use crate::command::{CommandRunner, combine_delete_outcomes};
use crate::enums::{Action, Protocol, RuleStatus};
use crate::error::UfwError;
use crate::parser::find_ip_status;

// ---------------------------------------------------------------------------
// Ipv4Builder  –  entry point via Firewall::ipv4()
// ---------------------------------------------------------------------------

pub struct Ipv4Builder<'a> {
    fw: &'a Firewall,
    ip: IpAddr,
}

impl<'a> Ipv4Builder<'a> {
    pub(crate) fn new(fw: &'a Firewall, ip: IpAddr) -> Self {
        Ipv4Builder { fw, ip }
    }

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

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

    pub fn delete(self) -> Ipv4DeleteBuilder {
        Ipv4DeleteBuilder { ip: self.ip }
    }

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

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

pub struct Ipv4ActionBuilder {
    ip: IpAddr,
    action: Action,
}

impl Ipv4ActionBuilder {
    pub fn tcp(self) -> Ipv4Finalizer {
        Ipv4Finalizer {
            action: self.action,
            ip: self.ip,
            protocol: Protocol::Tcp,
        }
    }

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

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

pub struct Ipv4Finalizer {
    ip: IpAddr,
    action: Action,
    protocol: Protocol,
}

impl Ipv4Finalizer {
    pub fn apply(&self) -> Result<(), UfwError> {
        let ip_str = self.ip.to_string();
        let action_str = match self.action {
            Action::Allow => "allow",
            Action::Deny => "deny",
        };
        match self.protocol {
            Protocol::Both => {
                CommandRunner::run("sudo", &["ufw", action_str, "from", &ip_str])?;
            }
            Protocol::Tcp | Protocol::Udp => {
                let proto_str = match self.protocol {
                    Protocol::Tcp => "tcp",
                    Protocol::Udp => "udp",
                    _ => unreachable!(),
                };
                CommandRunner::run(
                    "sudo",
                    &["ufw", action_str, "from", &ip_str, "proto", proto_str],
                )?;
            }
        }
        Ok(())
    }
}

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

pub struct Ipv4DeleteBuilder {
    ip: IpAddr,
}

impl Ipv4DeleteBuilder {
    pub fn tcp(self) -> Ipv4DeleteFinalizer {
        Ipv4DeleteFinalizer {
            ip: self.ip,
            protocol: Protocol::Tcp,
        }
    }

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

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

pub struct Ipv4DeleteFinalizer {
    ip: IpAddr,
    protocol: Protocol,
}

impl Ipv4DeleteFinalizer {
    pub fn apply(&self) -> Result<bool, UfwError> {
        let ip_str = self.ip.to_string();
        let outcomes: Vec<Result<_, _>> = match self.protocol {
            Protocol::Both => {
                vec![
                    // Generic (no protocol)
                    CommandRunner::delete("sudo", &["ufw", "delete", "allow", "from", &ip_str]),
                    CommandRunner::delete("sudo", &["ufw", "delete", "deny", "from", &ip_str]),
                    // Protocol-specific TCP
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", "from", &ip_str, "proto", "tcp"],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", "from", &ip_str, "proto", "tcp"],
                    ),
                    // Protocol-specific UDP
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", "from", &ip_str, "proto", "udp"],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", "from", &ip_str, "proto", "udp"],
                    ),
                ]
            }
            Protocol::Tcp => {
                vec![
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", "from", &ip_str, "proto", "tcp"],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", "from", &ip_str, "proto", "tcp"],
                    ),
                ]
            }
            Protocol::Udp => {
                vec![
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "allow", "from", &ip_str, "proto", "udp"],
                    ),
                    CommandRunner::delete(
                        "sudo",
                        &["ufw", "delete", "deny", "from", &ip_str, "proto", "udp"],
                    ),
                ]
            }
        };
        combine_delete_outcomes(outcomes)
    }
}

// ---------------------------------------------------------------------------
// Ipv4Status  –  read-only status check
// ---------------------------------------------------------------------------

pub struct Ipv4Status<'a> {
    fw: &'a Firewall,
    ip: IpAddr,
}

impl<'a> Ipv4Status<'a> {
    pub fn tcp(&self) -> Result<RuleStatus, UfwError> {
        let rules = self.fw.ips()?;
        Ok(find_ip_status(&rules, self.ip, Protocol::Tcp))
    }

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

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