use atomic_write_file::AtomicWriteFile; use defguard_wireguard_rs::{key::Key, net::IpAddrMask}; use log::info; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeSet, HashMap}, fs::File, io::{ErrorKind, Read, Write}, net::SocketAddr, path::PathBuf, time::SystemTime, }; use xdg::BaseDirectories; use crate::daemon::DaemonError; #[derive(Serialize, Deserialize, Clone)] pub enum Endpoint { Ip(SocketAddr), Domain(String, u16), } // subset of defguard_wireguard_rs::host::Peer, with hostname added #[derive(Serialize, Deserialize)] pub struct PeerConfig { pub psk: Option, pub ips: Vec<(IpAddrMask, Option)>, // if false: the hostnames are kept around for sharing, but we personally do not use them pub use_hostnames: bool, pub endpoint: Option, pub last_changed: SystemTime, pub known_to: Vec, pub mäsch_endpoint: SocketAddr, } fn default_wg_port() -> u16 { 51820 } #[derive(Serialize, Deserialize)] pub struct Network { pub privkey: String, // this really should be a different type, but this is what defguard takes... pub address: String, #[serde(default = "default_wg_port")] pub listen_port: u16, pub peers: HashMap, pub mäsch_port: u16, } #[derive(Serialize, Deserialize, Default)] pub struct Config { pub networks: HashMap, } pub fn load_config() -> Result<(PathBuf, Config), DaemonError> { let config_path = BaseDirectories::with_prefix("mäsch")?.place_state_file("daemon.json")?; let config: Config = match File::open(&config_path) { Ok(f) => serde_json::from_reader(f)?, Err(e) => match e.kind() { ErrorKind::NotFound => Config::default(), _ => Err(e)?, }, }; info!("read config"); Ok((config_path, config)) } pub fn write_config(conf: &Config, path: &PathBuf) -> Result<(), std::io::Error> { let mut f = AtomicWriteFile::open(path)?; serde_json::to_writer(&mut f, conf)?; f.commit()?; Ok(()) } pub fn sync_hostnames(hostnames: &BTreeSet<(String, String)>) -> Result<(), std::io::Error> { let mut hosts_str = match File::open("/etc/hosts") { Ok(mut f) => { let mut r = String::new(); f.read_to_string(&mut r)?; r } Err(e) => match e.kind() { ErrorKind::NotFound => "".to_owned(), _ => return Err(e), }, }; let seen_hostnames: BTreeSet = hosts_str .lines() .map(|l| { l.split_whitespace() .take_while(|dom| dom.chars().next().unwrap() != '#') .skip(1) }) .flatten() .map(|dom| dom.to_owned()) .collect(); for (ip, dom) in hostnames { if !seen_hostnames.contains(dom.as_str()) { hosts_str.push_str(ip); hosts_str.push(' '); hosts_str.push_str(&format!("{dom}")); hosts_str.push('\n'); } } info!("Syncing host file"); let mut f = AtomicWriteFile::open("/etc/hosts")?; f.write(hosts_str.as_bytes())?; f.commit()?; Ok(()) }