diff --git a/Cargo.lock b/Cargo.lock index 3700456..ce822cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,6 +326,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enum-as-inner" version = "0.5.1" @@ -470,6 +476,7 @@ dependencies = [ "serde_json", "sha2", "tempdir", + "trust-dns-client", ] [[package]] @@ -1514,6 +1521,15 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nom" version = "7.1.3" @@ -1639,6 +1655,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.4.6" @@ -2236,6 +2262,26 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trust-dns-client" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c408c32e6a9dbb38037cece35740f2cf23c875d8ca134d33631cec83f74d3fe" +dependencies = [ + "cfg-if", + "data-encoding", + "futures-channel", + "futures-util", + "lazy_static", + "radix_trie", + "rand 0.8.5", + "thiserror", + "time", + "tokio", + "tracing", + "trust-dns-proto", +] + [[package]] name = "trust-dns-proto" version = "0.22.0" diff --git a/Cargo.toml b/Cargo.toml index e246c0d..c283887 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ gix = { version = "0.44.1", features = [ tempdir = "0.3.7" serde = { version = "1.0.162", features = [ "derive" ]} serde_json = "1.0.96" +trust-dns-client = "0.22.0" diff --git a/src/domain.rs b/src/domain.rs new file mode 100644 index 0000000..ea64c55 --- /dev/null +++ b/src/domain.rs @@ -0,0 +1 @@ +pub mod checker; diff --git a/src/domain/checker.rs b/src/domain/checker.rs new file mode 100644 index 0000000..7747aad --- /dev/null +++ b/src/domain/checker.rs @@ -0,0 +1,105 @@ +use std::error::Error; +use std::str::FromStr; + +use trust_dns_client::client::{Client, SyncClient}; +use trust_dns_client::rr::{DNSClass, Name, RData, RecordType}; +use trust_dns_client::udp::UdpClientConnection; + +#[derive(Debug)] +pub enum NewDNSCheckerError { + InvalidResolverAddress, + InvalidTargetCNAME, + Unexpected(Box), +} + +impl From for NewDNSCheckerError { + fn from(e: E) -> NewDNSCheckerError { + NewDNSCheckerError::Unexpected(Box::from(e)) + } +} + +#[derive(Debug)] +pub enum CheckDomainError { + InvalidDomainName, + TargetCNAMENotSet, + ChallengeTokenNotSet, + Unexpected(Box), +} + +impl From for CheckDomainError { + fn from(e: E) -> CheckDomainError { + CheckDomainError::Unexpected(Box::from(e)) + } +} + +pub trait Checker { + fn check_domain(&self, domain: &str, challenge_token: &str) -> Result<(), CheckDomainError>; +} + +pub struct DNSChecker { + target_cname: Name, + client: SyncClient, +} + +impl DNSChecker { + pub fn new(target_cname: &str, resolver_addr: &str) -> Result { + let target_cname = + Name::from_str(target_cname).map_err(|_| NewDNSCheckerError::InvalidTargetCNAME)?; + + let resolver_addr = resolver_addr + .parse() + .map_err(|_| NewDNSCheckerError::InvalidResolverAddress)?; + + let conn = UdpClientConnection::new(resolver_addr)?; + let client = SyncClient::new(conn); + + Ok(DNSChecker { + target_cname, + client, + }) + } +} + +impl Checker for DNSChecker { + fn check_domain(&self, domain: &str, challenge_token: &str) -> Result<(), CheckDomainError> { + let mut fqdn = Name::from_str(domain).map_err(|_| CheckDomainError::InvalidDomainName)?; + fqdn.set_fqdn(true); + + // check that the CNAME is installed correctly on the domain + { + let response = self.client.query(&fqdn, DNSClass::IN, RecordType::CNAME)?; + + let records = response.answers(); + + if records.len() != 1 { + return Err(CheckDomainError::TargetCNAMENotSet); + } + + // if the single record isn't a CNAME, or it's not the target CNAME, then return + // TargetCNAMENotSet + match records[0].data() { + Some(RData::CNAME(remote_cname)) if remote_cname == &self.target_cname => (), + _ => return Err(CheckDomainError::TargetCNAMENotSet), + } + } + + // check that the TXT record with the challenge token is correctly installed on the domain + { + let fqdn = Name::from_str("_gateway")?.append_domain(&fqdn)?; + let response = self.client.query(&fqdn, DNSClass::IN, RecordType::TXT)?; + + let records = response.answers(); + + if !records.iter().any(|record| -> bool { + match record.data() { + Some(RData::TXT(txt)) => txt.to_string().contains(challenge_token), + _ => false, + } + }) { + return Err(CheckDomainError::ChallengeTokenNotSet); + } + } + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 5d9fa5f..1ad5afd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ pub mod origin; +pub mod domain; diff --git a/src/origin/store.rs b/src/origin/store.rs index 77aeda7..5cff29a 100644 --- a/src/origin/store.rs +++ b/src/origin/store.rs @@ -1,8 +1,6 @@ -use std::error::Error; - -use serde_json; - use super::Descr; +use serde_json; +use std::error::Error; #[derive(Clone, Copy)] pub struct Limits {} @@ -61,8 +59,6 @@ pub mod git { use std::path::{Path, PathBuf}; use std::{fs, io}; - use gix::progress::Discard; - pub struct Store<'a> { dir_path: &'a Path, } @@ -88,8 +84,10 @@ pub mod git { impl<'a> super::Store for Store<'a> { fn sync(&self, descr: &Descr, _limits: Limits) -> Result<(), SyncError> { - let should_interrupt = &core::sync::atomic::AtomicBool::new(false); + use gix::clone::Error as gixCloneErr; + use gix::progress::Discard; + let should_interrupt = &core::sync::atomic::AtomicBool::new(false); let repo_path = &self.repo_path(descr); // if the path doesn't exist then use the gix clone feature to clone it into the @@ -99,8 +97,6 @@ pub mod git { let Descr::Git { url, branch_name } = descr; - use gix::clone::Error as gixCloneErr; - let (repo, _) = gix::prepare_clone_bare(url.clone(), repo_path) .map_err(|e| match e { gixCloneErr::Init(gix::init::Error::InvalidBranchName { .. }) => { @@ -214,7 +210,6 @@ pub mod git { mod tests { use super::super::Store; use super::Descr; - use tempdir::TempDir; #[test]