wip: add missing files

This commit is contained in:
Hannaeko 2025-03-25 23:20:36 +01:00
parent c1d09cd391
commit 76aa894123
5 changed files with 346 additions and 0 deletions

119
src/resources/dns/external/record.rs vendored Normal file
View file

@ -0,0 +1,119 @@
use serde::{Deserialize, Serialize};
use super::rdata::RData;
use crate::resources::dns::internal;
use crate::{errors::Error, validation};
use crate::macros::{append_errors, push_error};
pub enum RecordError {
Validation { suberrors: Vec<Error> },
}
pub enum RecordValidationError {
NotInZone { name: String, zone: String },
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Record {
pub name: String,
pub ttl: u32,
#[serde(flatten)]
pub rdata: RData
}
impl From<internal::Record> for Record {
fn from(value: internal::Record) -> Self {
Record {
name: value.name.to_string(),
ttl: value.ttl,
rdata: value.rdata.into(),
}
}
}
impl Record {
fn validate(self, zone_name: &internal::Name) -> Result<internal::Record, Vec<Error>> {
let mut errors = Vec::new();
let name = push_error!(validation::normalize_domain(&self.name), errors, "/name").map(internal::Name::new);
let name = name.and_then(|name| {
if !name.ends_with(zone_name) {
errors.push(
Error::from(RecordValidationError::NotInZone { name: self.name, zone: zone_name.to_string() })
.with_path("/name")
);
None
} else {
Some(name)
}
});
// TODO: validate ttl
let rdata = append_errors!(self.rdata.validate(), errors, "/rdata");
if errors.is_empty() {
Ok(internal::Record {
name: name.unwrap(),
ttl: self.ttl,
rdata: rdata.unwrap(),
})
} else {
Err(errors)
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct RecordList(pub Vec<Record>);
impl From<internal::RecordList> for RecordList {
fn from(value: internal::RecordList) -> Self {
let records = value.records.into_iter().map(Record::from).collect();
RecordList(records)
}
}
impl RecordList {
fn validate(self, zone_name: &internal::Name) -> Result<internal::RecordList, Vec<Error>> {
let mut errors = Vec::new();
let mut records = Vec::new();
for (index, record) in self.0.into_iter().enumerate() {
let record = append_errors!(record.validate(zone_name), errors, &format!("/{index}"));
if let Some(record) = record {
records.push(record)
}
}
if errors.is_empty() {
Ok(internal::RecordList { records })
} else {
Err(errors)
}
}
}
#[derive(Debug,Deserialize)]
pub struct AddRecords {
pub new_records: RecordList
}
impl AddRecords {
pub fn validate(self, zone_name: &internal::Name) -> Result<internal::AddRecords, Error> {
let mut errors = Vec::new();
let records = append_errors!(self.new_records.validate(zone_name), errors, "/new_records");
if errors.is_empty() {
Ok(internal::AddRecords {
new_records: records.unwrap(),
})
} else {
Err(Error::from(RecordError::Validation { suberrors: errors }))
}
}
}

View file

@ -0,0 +1 @@
pub mod rdata;

View file

@ -0,0 +1,109 @@
use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub enum FriendlyRData {
Address(Address),
Service(Service),
MailServer(MailServer), // Include SPF, etc ?!
NameServer(NameServer),
TextData(TextData),
Alias(Alias),
}
pub enum FriendlyRType {
Address,
Service,
MailServer,
NameServer,
TextData,
Alias
}
pub struct FriendlyRecord {
owner: String,
friendly_type: FriendlyRType,
rdata: FriendlyRData,
ttl: u32,
}
pub struct RecordSet {
friendly_type: FriendlyRType,
records: Vec<FriendlyRecord>
}
pub struct RecordSetGroup {
owner: String,
rrsets: Vec<RecordSet>
}
pub struct FriendlyRecords(Vec<RecordSetGroup>);
#[derive(Debug, Deserialize, Serialize)]
pub struct Address {
pub address: String
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Service {
pub service_type: String,
pub port: u16,
pub weight: u16,
pub priority: u16,
pub server: String,
}
pub enum ServiceType {
ServiceProtocol { service_name: String, protocol: String },
Service { service_name: String },
None,
}
impl ServiceType {
pub fn from_name(name: String) -> (Self, String) {
let labels: Vec<_> = name.splitn(3, '.').collect();
let prefix: Vec<_> = labels.iter().cloned().take(2).map(|label| label.strip_prefix('_')).collect();
if prefix.is_empty() || prefix[0].is_none() {
(ServiceType::None, labels[0..].join(".").to_owned())
} else if prefix.len() == 1 || prefix[1].is_none() {
(ServiceType::Service { service_name: prefix[0].unwrap().into() }, labels[1..].join(".").to_owned())
} else {
(ServiceType::ServiceProtocol { service_name: prefix[0].unwrap().into(), protocol: prefix[1].unwrap().into() }, labels[2].to_owned())
}
}
}
impl fmt::Display for ServiceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ServiceType::ServiceProtocol { service_name, protocol } => write!(f, "{}/{}", service_name, protocol),
ServiceType::Service { service_name, } => write!(f, "{}", service_name),
ServiceType::None => write!(f, "-"),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct MailServer {
pub preference: u16,
pub mail_exchanger: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct NameServer {
pub target: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TextData {
pub text: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Alias {
pub target: String,
}

View file

@ -0,0 +1,90 @@
use std::fmt;
#[derive(Clone, Eq, PartialEq)]
pub struct Name {
name: String
}
impl Name {
pub fn new(name: String) -> Self {
Name {
name,
}
}
pub fn ends_with(&self, other: &Name) -> bool {
self.name == other.name || self.name.ends_with(&(String::from(".") + &other.name))
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl Ord for Name {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let mut labels = self.name.split('.').rev();
let mut other_labels = other.name.split('.').rev();
loop {
match (labels.next(), other_labels.next()) {
(Some(label), Some(other_label)) => match label.cmp(other_label) {
std::cmp::Ordering::Equal => (),
res => return res,
},
(None, Some(_)) => return std::cmp::Ordering::Less,
(Some(_), None) => return std::cmp::Ordering::Greater,
(None, None) => return std::cmp::Ordering::Equal,
}
}
}
}
impl PartialOrd for Name {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Eq, PartialEq)]
pub enum Rtype {
A,
Aaaa,
Cname,
Mx,
Ns,
Ptr,
Soa,
Srv,
Txt
}
impl Rtype {
pub fn value(&self) -> u16 {
match self {
Rtype::A => 1,
Rtype::Aaaa => 28,
Rtype::Cname => 5,
Rtype::Mx => 15,
Rtype::Ns => 2,
Rtype::Ptr => 12,
Rtype::Soa => 6,
Rtype::Srv => 33,
Rtype::Txt => 16,
}
}
}
impl Ord for Rtype {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value().cmp(&other.value())
}
}
impl PartialOrd for Rtype {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

View file

@ -0,0 +1,27 @@
use super::rdata::RData;
use super::Name;
#[derive(Clone)]
pub struct Record {
pub name: Name,
pub ttl: u32,
pub rdata: RData
}
#[derive(Clone)]
pub struct RecordList {
pub records: Vec<Record>,
}
impl RecordList {
pub fn sort(&mut self) {
self.records.sort_by(|r1, r2| {
let key1 = (&r1.name, r1.rdata.rtype());
let key2 = (&r2.name, r2.rdata.rtype());
key1.cmp(&key2)
});
}
}
pub struct AddRecords {
pub new_records: RecordList
}