use std::collections::HashMap; use std::error::Error; use std::fs; use std::net::Ipv6Addr; use ipnet::{IpBitOr, IpSub}; use std::str::FromStr; use log::{error, warn}; use serde_derive::{Deserialize, Serialize}; use systemd_journal_logger::connected_to_journal; use crate::cloudflare::DnsRecordType; use crate::cloudflare::DnsRecordType::{A, AAAA}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub(crate) struct InterfaceConfig { pub(crate) host_address: Ipv6Addr, pub(crate) interfaces: HashMap, } impl InterfaceConfig { pub(crate) fn load() -> Result> { let cfg: Self = match confy::load(env!("CARGO_PKG_NAME"),"interfaces") { Ok(data) => data, Err(e) => { match connected_to_journal() { true => error!("[ERROR] {e}"), false => eprintln!("[ERROR] {e}") } return Err(Box::new(e)); } }; Ok(cfg) } pub(crate) fn full_v6(&self, interface_name: &String, host_v6: Ipv6Addr) -> Result { let host_range = Ipv6Addr::from(host_v6.saturating_sub(self.host_address)); let interface_address = match self.interfaces.get(interface_name) { Some(address) => *address, None => { let err_msg = "Malformed IP in interfaces.toml"; match connected_to_journal() { true => error!("[ERROR] {err_msg}"), false => eprintln!("[ERROR] {err_msg}"), } return Err(()); } }; Ok(host_range.bitor(interface_address)) } } impl Default for InterfaceConfig { fn default() -> Self { InterfaceConfig { host_address: Ipv6Addr::from_str("::").expect("Malformed literal in code"), interfaces: HashMap::from([(" ".to_owned(), Ipv6Addr::from(0))]), } } } /////////////// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub(crate) struct ZoneEntry { pub(crate) name: String, pub(crate) r#type: Vec, pub(crate) interface: String, } impl Default for ZoneEntry { fn default() -> Self { ZoneEntry { name: " ".to_owned(), r#type: vec![A, AAAA], interface: " ".to_owned(), } } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub(crate) struct ZoneConfig { pub(crate) email: String, pub(crate) name: String, pub(crate) id: String, #[serde(alias="entry")] pub(crate) entries: Vec } impl ZoneConfig { pub(crate) fn load() -> Result, Box> { let path = confy::get_configuration_file_path(env!("CARGO_PKG_NAME"), "interfaces").expect("Something went wrong with confy"); let zones_dir = path.parent().expect("Something went wrong with confy").join("zones.d/"); let zones = fs::read_dir(zones_dir).expect("Directory creation handling does not yet exist"); let mut zone_configs: Vec = vec![]; for entry in zones { let entry = entry?; let path = entry.path(); if path.is_dir() { let warn_msg = "Subdirectory in zones.d detected, this should not be the case"; match connected_to_journal() { true => warn!("[WARN] {warn_msg}"), false => eprintln!("[WARN] {warn_msg}"), } } else if !path.file_name().expect("Directories should have been filtered out by the leading if") .to_str().expect("Conversion to str should not fail for a file name") .starts_with('.') { let zone_config_path = format!("zones.d/{}", path.file_stem() .expect("stem could not be extracted from filename").to_str() .expect("&OsStr could not be converted to &str")); match confy::load(env!("CARGO_PKG_NAME"), zone_config_path.as_str()) { Ok(data) => zone_configs.push(data), Err(e) => { match connected_to_journal() { true => error!("[ERROR] {e}"), false => eprintln!("[ERROR] {e}"), } return Err(Box::new(e)); } }; } } Ok(zone_configs) } } impl Default for ZoneConfig { fn default() -> Self { ZoneConfig { email: " ".to_owned(), name: " ".to_owned(), id: " ".to_owned(), entries: vec![ZoneEntry::default()], } } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub(crate) struct AppConfig { pub(crate) cloudflare_api_token: String, pub(crate) check_interval_seconds: Option, pub(crate) uptime_url: Option, } impl AppConfig { pub(crate) fn load() -> Result> { let cfg: Self = match confy::load(env!("CARGO_PKG_NAME"),"config") { Ok(data) => data, Err(e) => { match connected_to_journal() { true => error!("[ERROR] {e}"), false => eprintln!("[ERROR] {e}") } return Err(Box::new(e)); } }; if cfg.cloudflare_api_token.is_empty() { let err_msg = "Cloudflare api token not specified. The app cannot work without this"; match connected_to_journal() { true => error!("[ERROR] {err_msg}"), false => eprintln!("[ERROR] {err_msg}") } panic!("{err_msg}"); } Ok(cfg) } } impl Default for AppConfig { fn default() -> Self { Self { cloudflare_api_token: "".to_owned(), check_interval_seconds: None, uptime_url: None } } }