/* This file is part of gnix (https://codeberg.org/metamuffin/gnix) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin */ use crate::{ modules::{Node, NodeKind}, State, }; use anyhow::Context; use inotify::{EventMask, Inotify, WatchMask}; use log::{error, info}; use rand::random; use serde::{ de::{value, Error, SeqAccess, Visitor}, Deserialize, Deserializer, Serialize, }; use serde_yml::value::TaggedValue; use std::{ collections::BTreeMap, fmt, fs::read_to_string, marker::PhantomData, net::SocketAddr, ops::Deref, path::{Path, PathBuf}, sync::{Arc, RwLock}, }; #[derive(Deserialize)] pub struct Config { #[serde(default = "return_true")] pub watch_config: bool, pub http: Option, pub https: Option, #[serde(default = "random_bytes")] pub private_key: [u8; 32], #[serde(default)] pub limits: Limits, #[serde(default)] pub source_ip_from_header: bool, pub handler: DynNode, #[serde(default)] pub disable_server_header: bool, } fn random_bytes() -> [u8; 32] { [(); 32].map(|_| random()) } pub fn return_true() -> bool { true } #[derive(Debug, Serialize, Deserialize)] #[serde(default)] pub struct Limits { pub max_incoming_connections: usize, pub max_outgoing_connections: usize, pub max_incoming_connections_h3: usize, } #[derive(Debug, Serialize, Deserialize)] pub struct HttpConfig { #[serde(deserialize_with = "string_or_seq")] pub bind: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct HttpsConfig { #[serde(deserialize_with = "string_or_seq")] pub bind: Vec, #[serde(deserialize_with = "seq_or_not")] pub cert_path: Vec, pub cert_fallback: Option, #[serde(default)] pub disable_h3: bool, #[serde(default)] pub disable_h2: bool, #[serde(default)] pub disable_h1: bool, } // try deser Vec but fall back to deser T and putting that in Vec pub fn seq_or_not<'de, D, V: Deserialize<'de>>(des: D) -> Result, D::Error> where D: Deserializer<'de>, { struct SeqOrNot(PhantomData); impl<'de, V: Deserialize<'de>> Visitor<'de> for SeqOrNot { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a sequence or not a sequence") } fn visit_enum(self, data: A) -> Result where A: serde::de::EnumAccess<'de>, { Ok(vec![V::deserialize(value::EnumAccessDeserializer::new( data, ))?]) } fn visit_map(self, map: A) -> Result where A: serde::de::MapAccess<'de>, { Ok(vec![V::deserialize(value::MapAccessDeserializer::new( map, ))?]) } fn visit_str(self, val: &str) -> Result, E> where E: Error, { Ok(vec![V::deserialize(value::StrDeserializer::new(val))?]) } fn visit_seq(self, val: A) -> Result, A::Error> where A: SeqAccess<'de>, { Vec::::deserialize(value::SeqAccessDeserializer::new(val)) } } des.deserialize_any(SeqOrNot::(PhantomData)) } // fall back to expecting a single string and putting that in a 1-length vector fn string_or_seq<'de, D>(des: D) -> Result, D::Error> where D: Deserializer<'de>, { struct StringOrList; impl<'de> Visitor<'de> for StringOrList { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("sequence or list") } fn visit_str(self, val: &str) -> Result, E> where E: Error, { let addr = SocketAddr::deserialize(value::StrDeserializer::new(val))?; Ok(vec![addr]) } fn visit_seq(self, val: A) -> Result, A::Error> where A: SeqAccess<'de>, { Vec::::deserialize(value::SeqAccessDeserializer::new(val)) } } des.deserialize_any(StringOrList) } pub static NODE_KINDS: RwLock> = RwLock::new(BTreeMap::new()); #[derive(Clone)] pub struct DynNode(Arc); impl<'de> Deserialize<'de> for DynNode { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let tv = TaggedValue::deserialize(deserializer)?; let s = tv.tag.to_string(); let s = s.strip_prefix("!").unwrap_or(s.as_str()); let inst = NODE_KINDS .read() .unwrap() .get(s) .ok_or(serde::de::Error::unknown_variant(s, &[]))? .instanciate(tv.value) .map_err(|e| { let x = format!("instanciating module {s:?}: {e:?}"); serde::de::Error::custom(e.context(x)) })?; Ok(Self(inst)) } } impl Deref for DynNode { type Target = dyn Node; fn deref(&self) -> &Self::Target { self.0.as_ref() } } impl Config { pub fn load(path: &Path) -> anyhow::Result { info!("loading config from {path:?}"); let raw = read_to_string(path).context("reading config file")?; let config: Config = serde_yml::from_str(&raw).context("during parsing")?; Ok(config) } } impl Default for Limits { fn default() -> Self { Self { max_incoming_connections: 512, max_incoming_connections_h3: 4096, max_outgoing_connections: 256, } } } pub fn setup_file_watch(config_path: PathBuf, state: Arc) { std::thread::spawn(move || { let mut inotify = Inotify::init().unwrap(); inotify .watches() .add( config_path.parent().unwrap(), WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE, ) .unwrap(); let mut buffer = [0u8; 4096]; loop { let events = inotify .read_events_blocking(&mut buffer) .expect("Failed to read inotify events"); for event in events { if event.mask.contains(EventMask::MODIFY) { if config_path.metadata().map(|m| m.len()).unwrap_or_default() == 0 { continue; } match Config::load(&config_path) { Ok(conf) => { let mut r = state.config.blocking_write(); *r = Arc::new(conf) } Err(e) => error!("config has errors: {e:?}"), } } } } }); }