2023-12-25 15:24:18 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::error::Error;
|
|
|
|
use std::fs;
|
2023-12-26 03:10:00 +00:00
|
|
|
use std::net::Ipv6Addr;
|
2023-12-26 03:18:58 +00:00
|
|
|
use ipnet::{IpBitOr, IpSub};
|
2023-12-26 03:10:00 +00:00
|
|
|
use std::str::FromStr;
|
2023-12-25 15:24:18 +00:00
|
|
|
use log::{error, warn};
|
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2023-12-26 03:10:00 +00:00
|
|
|
use systemd_journal_logger::connected_to_journal;
|
|
|
|
use crate::cloudflare::DnsRecordType;
|
|
|
|
use crate::cloudflare::DnsRecordType::{A, AAAA};
|
2023-12-25 15:24:18 +00:00
|
|
|
|
2023-12-27 16:18:56 +00:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
2023-12-25 15:24:18 +00:00
|
|
|
pub(crate) struct InterfaceConfig {
|
2023-12-26 03:10:00 +00:00
|
|
|
pub(crate) host_address: Ipv6Addr,
|
|
|
|
pub(crate) interfaces: HashMap<String, Ipv6Addr>,
|
2023-12-25 15:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl InterfaceConfig {
|
|
|
|
pub(crate) fn load() -> Result<Self, Box<dyn Error>> {
|
|
|
|
let cfg: Self = match confy::load(env!("CARGO_PKG_NAME"),"interfaces") {
|
|
|
|
Ok(data) => data,
|
|
|
|
Err(e) => {
|
2023-12-26 03:06:38 +00:00
|
|
|
match connected_to_journal() {
|
|
|
|
true => error!("[ERROR] {e}"),
|
|
|
|
false => eprintln!("[ERROR] {e}")
|
|
|
|
}
|
2023-12-25 15:24:18 +00:00
|
|
|
return Err(Box::new(e));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(cfg)
|
|
|
|
}
|
2023-12-26 03:10:00 +00:00
|
|
|
|
|
|
|
pub(crate) fn full_v6(&self, interface_name: &String, host_v6: Ipv6Addr) -> Result<Ipv6Addr, ()> {
|
|
|
|
let host_range = Ipv6Addr::from(host_v6.saturating_sub(self.host_address));
|
|
|
|
let interface_address = match self.interfaces.get(interface_name) {
|
2023-12-26 03:18:58 +00:00
|
|
|
Some(address) => *address,
|
2023-12-26 03:10:00 +00:00
|
|
|
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(());
|
|
|
|
}
|
|
|
|
};
|
2023-12-27 20:07:15 +00:00
|
|
|
Ok(host_range.bitor(interface_address))
|
2023-12-26 03:10:00 +00:00
|
|
|
}
|
2023-12-25 15:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for InterfaceConfig {
|
|
|
|
fn default() -> Self {
|
|
|
|
InterfaceConfig {
|
2023-12-26 03:10:00 +00:00
|
|
|
host_address: Ipv6Addr::from_str("::").expect("Malformed literal in code"),
|
2023-12-26 03:18:58 +00:00
|
|
|
interfaces: HashMap::from([(" ".to_owned(), Ipv6Addr::from(0))]),
|
2023-12-25 15:24:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////
|
|
|
|
|
2023-12-27 16:18:56 +00:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
2023-12-25 15:24:18 +00:00
|
|
|
pub(crate) struct ZoneEntry {
|
|
|
|
pub(crate) name: String,
|
2023-12-26 03:10:00 +00:00
|
|
|
pub(crate) r#type: Vec<DnsRecordType>,
|
2023-12-25 15:24:18 +00:00
|
|
|
pub(crate) interface: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ZoneEntry {
|
|
|
|
fn default() -> Self {
|
|
|
|
ZoneEntry {
|
2023-12-26 03:18:58 +00:00
|
|
|
name: " ".to_owned(),
|
2023-12-26 03:10:00 +00:00
|
|
|
r#type: vec![A, AAAA],
|
2023-12-26 03:18:58 +00:00
|
|
|
interface: " ".to_owned(),
|
2023-12-25 15:24:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-27 16:18:56 +00:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
2023-12-25 15:24:18 +00:00
|
|
|
pub(crate) struct ZoneConfig {
|
|
|
|
pub(crate) email: String,
|
2023-12-26 03:10:00 +00:00
|
|
|
pub(crate) name: String,
|
2023-12-25 15:24:18 +00:00
|
|
|
pub(crate) id: String,
|
|
|
|
#[serde(alias="entry")]
|
|
|
|
pub(crate) entries: Vec<ZoneEntry>
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ZoneConfig {
|
|
|
|
pub(crate) fn load() -> Result<Vec<Self>, Box<dyn Error>> {
|
|
|
|
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/");
|
|
|
|
|
2023-12-27 19:55:51 +00:00
|
|
|
let zones = fs::read_dir(zones_dir).expect("Directory creation handling does not yet exist");
|
|
|
|
|
2023-12-25 15:24:18 +00:00
|
|
|
|
|
|
|
let mut zone_configs: Vec<Self> = vec![];
|
|
|
|
|
|
|
|
for entry in zones {
|
|
|
|
let entry = entry?;
|
|
|
|
let path = entry.path();
|
|
|
|
if path.is_dir() {
|
2023-12-26 03:06:38 +00:00
|
|
|
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}"),
|
|
|
|
}
|
2023-12-25 15:24:18 +00:00
|
|
|
}
|
2023-12-27 19:55:51 +00:00
|
|
|
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('.') {
|
2023-12-25 15:24:18 +00:00
|
|
|
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) => {
|
2023-12-26 03:06:38 +00:00
|
|
|
match connected_to_journal() {
|
|
|
|
true => error!("[ERROR] {e}"),
|
|
|
|
false => eprintln!("[ERROR] {e}"),
|
|
|
|
}
|
2023-12-25 15:24:18 +00:00
|
|
|
return Err(Box::new(e));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(zone_configs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ZoneConfig {
|
|
|
|
fn default() -> Self {
|
|
|
|
ZoneConfig {
|
2023-12-26 03:18:58 +00:00
|
|
|
email: " ".to_owned(),
|
|
|
|
name: " ".to_owned(),
|
|
|
|
id: " ".to_owned(),
|
2023-12-25 15:24:18 +00:00
|
|
|
entries: vec![ZoneEntry::default()],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-28 23:36:01 +00:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
|
|
|
pub(crate) struct AppConfig {
|
|
|
|
pub(crate) cloudflare_api_token: String,
|
|
|
|
pub(crate) check_interval_seconds: Option<u16>,
|
|
|
|
pub(crate) uptime_url: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AppConfig {
|
|
|
|
pub(crate) fn load() -> Result<Self, Box<dyn Error>> {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|