use crate::rootcheck;
use anyhow::{anyhow, Result};
use regex::Regex;
use std::fs::File;
use std::io::{Error, ErrorKind, Write};
use std::path::Path;
use std::process::Command;
#[derive(Default, Clone)]
pub struct UFWConf {
app_name: String,
config: String,
}
impl UFWConf {
pub fn is_root() -> bool {
rootcheck::escalate_if_needed()
}
pub fn append_ports(&mut self, port: &str, protocol: &str) -> Result<Self> {
let cfg = format_ports(port, protocol)?;
if self.config.chars().last().unwrap().eq(&'|') {
self.config.pop();
}
self.config = format!("{}|{}", self.config, cfg);
Ok(self.clone())
}
pub fn check_write_permission() -> bool {
match Command::new("ufw").arg("version").spawn() {
Ok(_) => !std::fs::metadata("/etc/ufw/applications.d/")
.unwrap()
.permissions()
.readonly(),
Err(_) => false,
}
}
pub fn init(app_name: &str, title: &str, description: &str) -> Result<Self> {
let app_name = app_name.replace(' ', "");
let config = format!(
"[{}]\ntitle={}\ndescription={}\nports=\n",
app_name, title, description,
);
Ok(Self { app_name, config })
}
pub fn get_config_string(&self) -> String {
self.config.clone()
}
pub fn try_write(&self) -> Result<(), Error> {
let path = format!("/etc/ufw/applications.d/ufw-{}", self.app_name);
if Path::new(path.as_str()).exists() {
std::fs::remove_file(path.as_str()).unwrap();
}
match File::create(path) {
Ok(mut f) => match f.write_all(self.config.as_bytes()) {
Ok(_) => Ok(()),
Err(e) => Err(e),
},
Err(e) => Err(e),
}
}
pub fn try_write_with_sudo(&self, allow: bool) -> Result<String, Error> {
let path = format!("/etc/ufw/applications.d/ufw-{}", self.app_name);
if Path::new(path.as_str()).exists() {
std::fs::remove_file(path.as_str()).unwrap();
}
match File::create(path) {
Ok(mut f) => match f.write_all(self.config.as_bytes()) {
Ok(_) => {
let mut x = Command::new("sudo");
match allow {
true => {
match x
.arg("ufw")
.arg("allow")
.arg(self.app_name.clone())
.output()
{
Ok(d) => Ok(String::from_utf8(d.stdout).unwrap()),
Err(e) => Err(e),
}
}
false => {
match x.arg("ufw").arg("deny").arg(self.app_name.clone()).output() {
Ok(d) => Ok(String::from_utf8(d.stdout).unwrap()),
Err(e) => Err(e),
}
}
}
}
Err(e) => Err(Error::new(
ErrorKind::Other,
format!("Error writing file {}", e),
)),
},
Err(e) => Err(Error::new(
ErrorKind::Other,
format!("Error creating file {}", e),
)),
}
}
pub fn try_adding_to_ufw(&self, allow: bool) -> Result<String> {
let path = format!("/etc/ufw/applications.d/ufw-{}", self.app_name);
if Path::new(path.as_str()).exists() {
std::fs::remove_file(path.as_str()).unwrap();
}
match File::create(path) {
Ok(mut f) => match f.write_all(self.config.as_bytes()) {
Ok(_) => {
let mut x = Command::new("ufw");
match allow {
true => match x.arg("allow").arg(self.app_name.clone()).output() {
Ok(d) => Ok(String::from_utf8(d.stdout).unwrap()),
Err(_) => {
let mut x = Command::new("sudo");
match x
.arg("ufw")
.arg("allow")
.arg(self.app_name.clone())
.output()
{
Ok(d) => Ok(String::from_utf8(d.stdout).unwrap()),
Err(e) => Err(anyhow!(format!(
"Error running ufw deny {}: {}",
self.app_name, e
),)),
}
}
},
false => match x.arg("deny").arg(self.app_name.clone()).output() {
Ok(d) => Ok(String::from_utf8(d.stdout).unwrap()),
Err(e) => Err(anyhow!("{}", e)),
},
}
}
Err(e) => Err(anyhow!(format!("Error writing file {}", e),)),
},
Err(e) => Err(anyhow!(format!("Error creating file {}", e),)),
}
}
}
fn format_ports(port: &str, protocol: &str) -> Result<String> {
check_ports(port, protocol)?;
let protocol = if protocol.is_empty() {
String::new()
} else {
format!("/{protocol}")
};
Ok(format!("{port}{protocol}|"))
}
#[inline]
fn check_ports(port_range: &str, protocol: &str) -> Result<()> {
let port_pattern = Regex::new(r"^\d+:\d+$")?;
if !["tcp", "udp", ""].contains(&protocol) {
return Err(anyhow!("Invalid protocol {port_range}={protocol}"));
}
if !port_pattern.is_match(port_range) {
return Err(anyhow!("Bad port/range at {port_range}={protocol}"));
}
Ok(())
}