314 lines
9.2 KiB
Rust
314 lines
9.2 KiB
Rust
use std::net::{Ipv6Addr, Ipv4Addr};
|
|
use std::fmt;
|
|
use std::ops::{Deref, DerefMut};
|
|
|
|
|
|
use rocket::{Request, State, http::Status, request::{FromParam, FromRequest, Outcome}};
|
|
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
use tokio::{net::TcpStream as TokioTcpStream, task};
|
|
|
|
use trust_dns_client::{client::AsyncClient, serialize::binary::BinEncoder, tcp::TcpClientStream};
|
|
use trust_dns_proto::error::{ProtoError};
|
|
use trust_dns_proto::iocompat::AsyncIoTokioAsStd;
|
|
|
|
|
|
use super::trust_dns_types::{self, Name};
|
|
use crate::config::Config;
|
|
use crate::models::errors::make_500;
|
|
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
#[serde(tag = "Type")]
|
|
#[serde(rename_all = "UPPERCASE")]
|
|
pub enum RData {
|
|
#[serde(rename_all = "PascalCase")]
|
|
A {
|
|
address: Ipv4Addr
|
|
},
|
|
#[serde(rename_all = "PascalCase")]
|
|
AAAA {
|
|
address: Ipv6Addr
|
|
},
|
|
#[serde(rename_all = "PascalCase")]
|
|
CAA {
|
|
issuer_critical: bool,
|
|
value: String,
|
|
property_tag: String,
|
|
},
|
|
#[serde(rename_all = "PascalCase")]
|
|
CNAME {
|
|
target: String
|
|
},
|
|
// HINFO(HINFO),
|
|
// HTTPS(SVCB),
|
|
#[serde(rename_all = "PascalCase")]
|
|
MX {
|
|
preference: u16,
|
|
mail_exchanger: String
|
|
},
|
|
// NAPTR(NAPTR),
|
|
#[serde(rename_all = "PascalCase")]
|
|
NULL {
|
|
data: String
|
|
},
|
|
#[serde(rename_all = "PascalCase")]
|
|
NS {
|
|
target: String
|
|
},
|
|
// OPENPGPKEY(OPENPGPKEY),
|
|
// OPT(OPT),
|
|
#[serde(rename_all = "PascalCase")]
|
|
PTR {
|
|
target: String
|
|
},
|
|
#[serde(rename_all = "PascalCase")]
|
|
SOA {
|
|
master_server_name: String,
|
|
maintainer_name: String,
|
|
refresh: i32,
|
|
retry: i32,
|
|
expire: i32,
|
|
minimum: u32,
|
|
serial: u32
|
|
},
|
|
#[serde(rename_all = "PascalCase")]
|
|
SRV {
|
|
server: String,
|
|
port: u16,
|
|
priority: u16,
|
|
weight: u16,
|
|
},
|
|
#[serde(rename_all = "PascalCase")]
|
|
SSHFP {
|
|
algorithm: u8,
|
|
digest_type: u8,
|
|
fingerprint: String,
|
|
},
|
|
// SVCB(SVCB),
|
|
// TLSA(TLSA),
|
|
#[serde(rename_all = "PascalCase")]
|
|
TXT {
|
|
text: String
|
|
},
|
|
|
|
// TODO: Eventually allow deserialization of DNSSEC records
|
|
#[serde(skip)]
|
|
DNSSEC(trust_dns_types::DNSSECRData),
|
|
#[serde(rename_all = "PascalCase")]
|
|
Unknown {
|
|
code: u16,
|
|
data: String,
|
|
},
|
|
// ZERO,
|
|
}
|
|
|
|
impl From<trust_dns_types::RData> for RData {
|
|
fn from(rdata: trust_dns_types::RData) -> RData {
|
|
match rdata {
|
|
trust_dns_types::RData::A(address) => RData::A { address },
|
|
trust_dns_types::RData::AAAA(address) => RData::AAAA { address },
|
|
// Still a draft, no iana number yet, I don't to put something that is not currently supported so that's why NULL and not unknown.
|
|
// TODO: probably need better error here, I don't know what to do about that as this would require to change the From for something else.
|
|
// (empty data because I'm lazy)
|
|
trust_dns_types::RData::ANAME(_) => RData::NULL {
|
|
data: String::new()
|
|
},
|
|
trust_dns_types::RData::CNAME(target) => RData::CNAME {
|
|
target: target.to_utf8()
|
|
},
|
|
trust_dns_types::RData::CAA(caa) => RData::CAA {
|
|
issuer_critical: caa.issuer_critical(),
|
|
value: format!("{}", CAAValue(caa.value())),
|
|
property_tag: caa.tag().as_str().to_string(),
|
|
},
|
|
trust_dns_types::RData::MX(mx) => RData::MX {
|
|
preference: mx.preference(),
|
|
mail_exchanger: mx.exchange().to_utf8()
|
|
},
|
|
trust_dns_types::RData::NULL(null) => RData::NULL {
|
|
data: base64::encode(null.anything().map(|data| data.to_vec()).unwrap_or_default())
|
|
},
|
|
trust_dns_types::RData::NS(target) => RData::NS {
|
|
target: target.to_utf8()
|
|
},
|
|
trust_dns_types::RData::PTR(target) => RData::PTR {
|
|
target: target.to_utf8()
|
|
},
|
|
trust_dns_types::RData::SOA(soa) => RData::SOA {
|
|
master_server_name: soa.mname().to_utf8(),
|
|
maintainer_name: soa.rname().to_utf8(),
|
|
refresh: soa.refresh(),
|
|
retry: soa.retry(),
|
|
expire: soa.expire(),
|
|
minimum: soa.minimum(),
|
|
serial: soa.serial()
|
|
},
|
|
trust_dns_types::RData::SRV(srv) => RData::SRV {
|
|
server: srv.target().to_utf8(),
|
|
port: srv.port(),
|
|
priority: srv.priority(),
|
|
weight: srv.weight(),
|
|
},
|
|
trust_dns_types::RData::SSHFP(sshfp) => RData::SSHFP {
|
|
algorithm: sshfp.algorithm().into(),
|
|
digest_type: sshfp.fingerprint_type().into(),
|
|
fingerprint: trust_dns_types::sshfp::HEX.encode(sshfp.fingerprint()),
|
|
},
|
|
trust_dns_types::RData::TXT(txt) => RData::TXT { text: format!("{}", txt) },
|
|
trust_dns_types::RData::DNSSEC(data) => RData::DNSSEC(data),
|
|
rdata => {
|
|
let code = rdata.to_record_type().into();
|
|
let mut data = Vec::new();
|
|
let mut encoder = BinEncoder::new(&mut data);
|
|
// TODO: need better error handling (use TryFrom ?)
|
|
rdata.emit(&mut encoder).expect("could not encode data");
|
|
|
|
RData::Unknown {
|
|
code,
|
|
data: base64::encode(data),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct CAAValue<'a>(&'a trust_dns_types::caa::Value);
|
|
|
|
// trust_dns Display implementation panics if no parameters
|
|
// Implementation based on caa::emit_value
|
|
// Also the quotes are strips to render in JSON
|
|
impl<'a> fmt::Display for CAAValue<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
match self.0 {
|
|
trust_dns_types::caa::Value::Issuer(name, parameters) => {
|
|
|
|
if let Some(name) = name {
|
|
write!(f, "{}", name)?;
|
|
}
|
|
|
|
if name.is_none() && parameters.is_empty() {
|
|
write!(f, ";")?;
|
|
}
|
|
|
|
for value in parameters {
|
|
write!(f, "; {}", value)?;
|
|
}
|
|
}
|
|
trust_dns_types::caa::Value::Url(url) => write!(f, "{}", url)?,
|
|
trust_dns_types::caa::Value::Unknown(v) => write!(f, "{:?}", v)?,
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
pub enum DNSClass {
|
|
IN,
|
|
CH,
|
|
HS,
|
|
NONE,
|
|
ANY,
|
|
OPT(u16),
|
|
}
|
|
|
|
impl From<trust_dns_types::DNSClass> for DNSClass {
|
|
fn from(dns_class: trust_dns_types::DNSClass) -> DNSClass {
|
|
match dns_class {
|
|
trust_dns_types::DNSClass::IN => DNSClass::IN,
|
|
trust_dns_types::DNSClass::CH => DNSClass::CH,
|
|
trust_dns_types::DNSClass::HS => DNSClass::HS,
|
|
trust_dns_types::DNSClass::NONE => DNSClass::NONE,
|
|
trust_dns_types::DNSClass::ANY => DNSClass::ANY,
|
|
trust_dns_types::DNSClass::OPT(v) => DNSClass::OPT(v),
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
pub struct Record {
|
|
#[serde(rename = "Name")]
|
|
pub name: String,
|
|
#[serde(rename = "Class")]
|
|
pub dns_class: DNSClass,
|
|
#[serde(rename = "TTL")]
|
|
pub ttl: u32,
|
|
#[serde(flatten)]
|
|
pub rdata: RData,
|
|
}
|
|
|
|
impl From<trust_dns_types::Record> for Record {
|
|
fn from(record: trust_dns_types::Record) -> Record {
|
|
Record {
|
|
name: record.name().to_utf8(),
|
|
//rr_type: record.rr_type().into(),
|
|
dns_class: record.dns_class().into(),
|
|
ttl: record.ttl(),
|
|
rdata: record.into_data().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct AbsoluteName(Name);
|
|
|
|
impl<'r> FromParam<'r> for AbsoluteName {
|
|
type Error = ProtoError;
|
|
|
|
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
|
let mut name = Name::from_utf8(¶m).unwrap();
|
|
if !name.is_fqdn() {
|
|
name.set_fqdn(true);
|
|
}
|
|
Ok(AbsoluteName(name))
|
|
}
|
|
}
|
|
|
|
impl Deref for AbsoluteName {
|
|
type Target = Name;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
pub struct DnsClient(AsyncClient);
|
|
|
|
impl Deref for DnsClient {
|
|
type Target = AsyncClient;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl DerefMut for DnsClient {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
|
|
#[rocket::async_trait]
|
|
impl<'r> FromRequest<'r> for DnsClient {
|
|
type Error = ();
|
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
|
let config = try_outcome!(request.guard::<State<Config>>().await);
|
|
let (stream, handle) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(config.dns.server);
|
|
let client = AsyncClient::with_timeout(
|
|
stream,
|
|
handle,
|
|
std::time::Duration::from_secs(5),
|
|
None);
|
|
let (client, bg) = match client.await {
|
|
Err(e) => {
|
|
println!("Failed to connect to DNS server {:#?}", e);
|
|
return Outcome::Failure((Status::InternalServerError, ()))
|
|
},
|
|
Ok(c) => c
|
|
};
|
|
task::spawn(bg);
|
|
Outcome::Success(DnsClient(client))
|
|
}
|
|
}
|