nomilo/src/resources/dns/external/rdata.rs

438 lines
11 KiB
Rust
Raw Normal View History

2025-03-24 22:03:54 +00:00
use std::fmt::Write;
use std::net::{Ipv4Addr, Ipv6Addr};
use domain::base::{Rtype, scan::Symbol};
use serde::{Deserialize, Serialize};
use crate::errors::Error;
use crate::validation;
use crate::macros::{append_errors, push_error};
use crate::resources::record::RecordParseError;
use crate::resources::dns::internal;
/// 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 rtype(&self) -> Rtype {
match self {
RData::A(_) => Rtype::A,
RData::Aaaa(_) => Rtype::AAAA,
RData::Cname(_) => Rtype::CNAME,
RData::Mx(_) => Rtype::MX,
RData::Ns(_) => Rtype::NS,
RData::Ptr(_) => Rtype::PTR,
RData::Soa(_) => Rtype::SOA,
RData::Srv(_) => Rtype::SRV,
RData::Txt(_) => Rtype::TXT,
}
}
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();
let address = push_error!(self.address.parse::<Ipv4Addr>().map_err(|e| {
Error::from(RecordParseError::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(RecordParseError::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)
}
}
}