From 532517cf478ec6ea1e6249694943fc132af6d9ed Mon Sep 17 00:00:00 2001 From: NiklasVousten <24965952+NiklasVousten@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:32:21 +0100 Subject: [PATCH] Added config validation --- README.md | 7 +++++ src/blocklist.rs | 4 +-- src/main.rs | 73 ++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 141b205..d23d385 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,13 @@ tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com" trust_nx_responses = false ``` +## Validation +The config can be validated by running the following command. + +`cargo run -- --validate` + +This only validates the config, block- and allowlists, and does not start the DNS server. If the validation fails, the program exits with the error code `1`. + ## DNSSEC Issues Due to an upstream issue of [hickory-dns](https://github.com/hickory-dns/hickory-dns/issues/2429), non DNSSEC sites will not be resolved if `validate = true`. Only DNSSEC capable sites will be resolved with this setting. diff --git a/src/blocklist.rs b/src/blocklist.rs index a7d3c12..52b3ff7 100644 --- a/src/blocklist.rs +++ b/src/blocklist.rs @@ -88,7 +88,7 @@ impl BlockList { // block list for url in adlist { - let (raw_list, mut list_errors) = get_file(url, restore_from_cache).await; + let (raw_list, mut list_errors) = get_file(url, restore_from_cache, true).await; match raw_list { None => { error!("skipp list {url}"); @@ -141,7 +141,7 @@ impl BlockList { // allow list for url in allow_list { info!("load allow list"); - let (raw_list, mut list_errors) = get_file(url, restore_from_cache).await; + let (raw_list, mut list_errors) = get_file(url, restore_from_cache, true).await; match raw_list { None => error!("skipp list {url}"), Some(raw_list) => { diff --git a/src/main.rs b/src/main.rs index 11ef89c..62e495b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,16 +23,10 @@ use reqwest::Client; use rustls::{Certificate, PrivateKey}; use serde::Deserialize; use std::{ - env::var, - fs::{self, File}, - io::BufReader, - iter, - path::{Path, PathBuf}, - sync::{ + env::var, fs::{self, File}, io::BufReader, iter, path::{Path, PathBuf}, sync::{ atomic::{AtomicU64, Ordering}, Arc - }, - time::Duration + }, time::Duration }; use time::OffsetDateTime; use tokio::{ @@ -244,10 +238,12 @@ fn load_cert_and_key( Ok((certificates, key)) } -/// load a text file from url and cache it. -/// If restore_from_cache is true only the cache is used. -/// Return None if an Err has occure. -async fn get_file(url: &Url, restore_from_cache: bool) -> (Option, String) { +/// Load a text file from url and cache it. +/// If restore_from_cache is true, only the cache is used. +/// The first return value is the file content. +/// It will be None if an error has occured. +/// The second value is a combined error message. +async fn get_file(url: &Url, restore_from_cache: bool, cache_file: bool) -> (Option, String) { if url.scheme() == "file" { let path = url.path(); info!("load file {path:?}"); @@ -281,12 +277,13 @@ async fn get_file(url: &Url, restore_from_cache: bool) -> (Option, Strin .error_for_status()? .text() .await?; + if cache_file { if let Err(err) = write(&path, &resp) .await .with_context(|| format!("failed to save to {path:?}")) { error!("{err:?}"); - } + }} Ok(resp) } .await; @@ -493,6 +490,19 @@ fn main() { Lazy::force(&CONFIG_PATH); Lazy::force(&LIST_DIR); + let config = load_config(); + + if std::env::args().any(|x| x == "--validate") { + if !async_validate_config(config) { + error!("Config validation failed!"); + std::process::exit(1); + } + } else { + async_main(config); + } +} + +fn load_config() -> Config { info!("load config from {:?}", &*CONFIG_PATH); let config = fs::read(&*CONFIG_PATH) .with_context(|| format!("Failed to read {:?}", CONFIG_PATH.as_path())) @@ -501,7 +511,42 @@ fn main() { .with_context(|| "Failed to deserialize config") .unwrap_or_else(|err| panic!("{err:?}")); debug!("{:#?}", config); - async_main(config); + + config +} + +#[tokio::main] +async fn async_validate_config(config: Config) -> bool{ + let mut validated = true; + //Allow List + for list in config.blocklist.allow_list { + let (file_content, error_message) = get_file(&list, false, false).await; + if let Some(content) = file_content { + if let Err(err) = parser::Blocklist::parse(list.path(), &content) { + error!("{}", err.msg()); + validated = false; + } + } else { + error!("{error_message}"); + validated = false; + } + } + + //Block List + for list in config.blocklist.lists { + let (file_content, error_message) = get_file(&list, false, false).await; + if let Some(content) = file_content { + if let Err(err) = parser::Blocklist::parse(list.path(), &content) { + error!("{}", err.msg()); + validated = false; + } + } else { + error!("{error_message}"); + validated = false; + } + } + + validated } #[cfg(test)]