430 lines
10 KiB
Rust
430 lines
10 KiB
Rust
use std::fmt::Write;
|
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
|
|
|
use domain::base::scan::Symbol;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::errors::Error;
|
|
use crate::validation;
|
|
|
|
use crate::macros::{append_errors, push_error};
|
|
use crate::resources::dns::internal;
|
|
|
|
pub enum RDataValidationError {
|
|
Ip4Address { input: String },
|
|
Ip6Address { input: String },
|
|
IpAddress { input: String },
|
|
Number { min: i128, max: i128 }
|
|
}
|
|
|
|
/// Type used to serialize / deserialize resource records data to response / request
|
|
///
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
#[serde(tag = "type", content = "rdata")]
|
|
#[serde(rename_all = "UPPERCASE")]
|
|
pub enum RData {
|
|
A(A),
|
|
Aaaa(Aaaa),
|
|
// TODO: CAA
|
|
Cname(Cname),
|
|
// TODO: DS
|
|
Mx(Mx),
|
|
Ns(Ns),
|
|
Ptr(Ptr),
|
|
Soa(Soa),
|
|
Srv(Srv),
|
|
// TODO: SSHFP
|
|
// TODO: SVCB / HTTPS
|
|
// TODO: TLSA
|
|
Txt(Txt),
|
|
}
|
|
|
|
impl RData {
|
|
pub fn validate(self) -> Result<internal::RData, Vec<Error>> {
|
|
let rdata = match self {
|
|
RData::A(data) => internal::RData::A(data.validate()?),
|
|
RData::Aaaa(data) => internal::RData::Aaaa(data.validate()?),
|
|
RData::Cname(data) => internal::RData::Cname(data.validate()?),
|
|
RData::Mx(data) => internal::RData::Mx(data.validate()?),
|
|
RData::Ns(data) => internal::RData::Ns(data.validate()?),
|
|
RData::Ptr(data) => internal::RData::Ptr(data.validate()?),
|
|
RData::Soa(data) => internal::RData::Soa(data.validate()?),
|
|
RData::Srv(data) => internal::RData::Srv(data.validate()?),
|
|
RData::Txt(data) => internal::RData::Txt(data.validate()?),
|
|
};
|
|
|
|
Ok(rdata)
|
|
}
|
|
}
|
|
|
|
impl From<internal::RData> for RData {
|
|
fn from(value: internal::RData) -> Self {
|
|
match value {
|
|
internal::RData::A(data) => RData::A(data.into()),
|
|
internal::RData::Aaaa(data) => RData::Aaaa(data.into()),
|
|
internal::RData::Cname(data) => RData::Cname(data.into()),
|
|
internal::RData::Mx(data) => RData::Mx(data.into()),
|
|
internal::RData::Ns(data) => RData::Ns(data.into()),
|
|
internal::RData::Ptr(data) => RData::Ptr(data.into()),
|
|
internal::RData::Soa(data) => RData::Soa(data.into()),
|
|
internal::RData::Srv(data) => RData::Srv(data.into()),
|
|
internal::RData::Txt(data) => RData::Txt(data.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------- A --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct A {
|
|
pub address: String,
|
|
}
|
|
|
|
impl From<internal::A> for A {
|
|
fn from(value: internal::A) -> Self {
|
|
A {
|
|
address: value.address.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl A {
|
|
pub fn validate(self) -> Result<internal::A, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
// TODO: replace with custom validation
|
|
let address = push_error!(self.address.parse::<Ipv4Addr>().map_err(|e| {
|
|
Error::from(RDataValidationError::Ip4Address { input: self.address })
|
|
.with_cause(&e.to_string())
|
|
.with_path("/address")
|
|
}), errors);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::A {
|
|
address: address.unwrap()
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------- AAAA --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Aaaa {
|
|
pub address: String,
|
|
}
|
|
|
|
impl From<internal::Aaaa> for Aaaa {
|
|
fn from(value: internal::Aaaa) -> Self {
|
|
Aaaa {
|
|
address: value.address.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Aaaa {
|
|
pub fn validate(self) -> Result<internal::Aaaa, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
// TODO: replace with custom validation
|
|
let address = push_error!(self.address.parse::<Ipv6Addr>().map_err(|e| {
|
|
Error::from(RDataValidationError::Ip6Address { input: self.address })
|
|
.with_cause(&e.to_string())
|
|
.with_path("/address")
|
|
}), errors);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::Aaaa {
|
|
address: address.unwrap()
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------- CNAME --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Cname {
|
|
pub target: String,
|
|
}
|
|
|
|
impl From<internal::Cname> for Cname {
|
|
fn from(value: internal::Cname) -> Self {
|
|
Cname {
|
|
target: value.target.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Cname {
|
|
pub fn validate(self) -> Result<internal::Cname, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
let cname = push_error!(
|
|
validation::normalize_domain(&self.target),
|
|
errors, "/target"
|
|
);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::Cname {
|
|
target: internal::Name::new(cname.unwrap())
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------- MX --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Mx {
|
|
// TODO: Validate number
|
|
pub preference: u16,
|
|
pub mail_exchanger: String,
|
|
}
|
|
|
|
impl From<internal::Mx> for Mx {
|
|
fn from(value: internal::Mx) -> Self {
|
|
Mx {
|
|
preference: value.preference,
|
|
mail_exchanger: value.mail_exchanger.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Mx {
|
|
pub fn validate(self) -> Result<internal::Mx, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
let mail_exchanger = push_error!(
|
|
validation::normalize_domain(&self.mail_exchanger),
|
|
errors, "/mail_exchanger"
|
|
);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::Mx {
|
|
preference: self.preference,
|
|
mail_exchanger: internal::Name::new(mail_exchanger.unwrap()),
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------- NS --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Ns {
|
|
pub target: String,
|
|
}
|
|
|
|
impl From<internal::Ns> for Ns {
|
|
fn from(value: internal::Ns) -> Self {
|
|
Ns {
|
|
target: value.target.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Ns {
|
|
pub fn validate(self) -> Result<internal::Ns, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
let target = push_error!(
|
|
validation::normalize_domain(&self.target),
|
|
errors, "/target"
|
|
);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::Ns {
|
|
target: internal::Name::new(target.unwrap()),
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------- PTR --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Ptr {
|
|
pub target: String,
|
|
}
|
|
|
|
impl From<internal::Ptr> for Ptr {
|
|
fn from(value: internal::Ptr) -> Self {
|
|
Ptr {
|
|
target: value.target.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Ptr {
|
|
pub fn validate(self) -> Result<internal::Ptr, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
let target = push_error!(
|
|
validation::normalize_domain(&self.target),
|
|
errors, "/target"
|
|
);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::Ptr {
|
|
target: internal::Name::new(target.unwrap()),
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* --------- SOA --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Soa {
|
|
pub primary_server: String,
|
|
pub maintainer: String,
|
|
pub refresh: u32,
|
|
pub retry: u32,
|
|
pub expire: u32,
|
|
pub minimum: u32,
|
|
pub serial: u32,
|
|
}
|
|
|
|
impl From<internal::Soa> for Soa {
|
|
fn from(value: internal::Soa) -> Self {
|
|
Soa {
|
|
primary_server: value.primary_server.to_string(),
|
|
maintainer: value.maintainer.to_string(),
|
|
refresh: value.refresh,
|
|
retry: value.retry,
|
|
expire: value.expire,
|
|
minimum: value.minimum,
|
|
serial: value.serial,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Soa {
|
|
pub fn validate(self) -> Result<internal::Soa, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
let primary_server = push_error!(
|
|
validation::normalize_domain(&self.primary_server),
|
|
errors, "/primary_server"
|
|
);
|
|
|
|
let maintainer = push_error!(
|
|
validation::normalize_domain(&self.maintainer),
|
|
errors, "/maintainer"
|
|
);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::Soa {
|
|
primary_server: internal::Name::new(primary_server.unwrap()),
|
|
maintainer: internal::Name::new(maintainer.unwrap()),
|
|
refresh: self.refresh,
|
|
retry: self.retry,
|
|
expire: self.expire,
|
|
minimum: self.minimum,
|
|
serial: self.serial,
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------- SRV --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Srv {
|
|
pub server: String,
|
|
pub port: u16,
|
|
pub priority: u16,
|
|
pub weight: u16,
|
|
}
|
|
|
|
impl From<internal::Srv> for Srv {
|
|
fn from(value: internal::Srv) -> Self {
|
|
Srv {
|
|
server: value.server.to_string(),
|
|
port: value.port,
|
|
priority: value.priority,
|
|
weight: value.weight,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Srv {
|
|
pub fn validate(self) -> Result<internal::Srv, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
let server = push_error!(
|
|
validation::normalize_domain(&self.server),
|
|
errors, "/server"
|
|
);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::Srv {
|
|
server: internal::Name::new(server.unwrap()),
|
|
priority: self.priority,
|
|
weight: self.weight,
|
|
port: self.port,
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* --------- TXT --------- */
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Txt {
|
|
pub text: String,
|
|
}
|
|
|
|
impl From<internal::Txt> for Txt {
|
|
fn from(value: internal::Txt) -> Self {
|
|
|
|
let mut concatenated_text = String::new();
|
|
for c in value.text.iter() {
|
|
// Escapes '\' and non printable chars
|
|
let c = Symbol::display_from_octet(*c);
|
|
write!(concatenated_text, "{}", c).unwrap();
|
|
}
|
|
|
|
Txt {
|
|
text: concatenated_text
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Txt {
|
|
pub fn validate(self) -> Result<internal::Txt, Vec<Error>> {
|
|
let mut errors = Vec::new();
|
|
|
|
let text = append_errors!(
|
|
validation::parse_txt_data(&self.text),
|
|
errors, "/text"
|
|
);
|
|
|
|
if errors.is_empty() {
|
|
Ok(internal::Txt {
|
|
text: text.unwrap()
|
|
})
|
|
} else {
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|