adns_zone/
lib.rs

1use adns_proto::{Class, Name, Question, Record, SoaData, Type, TypeData, TypeDataParseError};
2use indexmap::{map::Entry, IndexMap};
3use log::warn;
4use serde::{ser::SerializeSeq, Deserialize, Serialize};
5use serde_with::{serde_as, DeserializeAs, SerializeAs};
6
7mod updates;
8pub use updates::*;
9
10struct VecRecordConvert;
11
12impl SerializeAs<Vec<Record>> for VecRecordConvert {
13    fn serialize_as<S: serde::Serializer>(
14        source: &Vec<Record>,
15        serializer: S,
16    ) -> Result<S::Ok, S::Error> {
17        let mut seq = serializer.serialize_seq(Some(source.len()))?;
18        for item in source {
19            let item: ZoneRecord = item.clone().into();
20            seq.serialize_element(&item)?;
21        }
22        seq.end()
23    }
24}
25
26impl<'de> DeserializeAs<'de, Vec<Record>> for VecRecordConvert {
27    fn deserialize_as<D: serde::Deserializer<'de>>(
28        deserializer: D,
29    ) -> Result<Vec<Record>, D::Error> {
30        let from = Vec::<ZoneRecord>::deserialize(deserializer)?;
31        from.into_iter()
32            .map(|x| -> Result<Record, _> { x.try_into() })
33            .collect::<Result<Vec<Record>, TypeDataParseError>>()
34            .map_err(serde::de::Error::custom)
35    }
36}
37
38struct SubZoneConvert;
39
40impl SerializeAs<IndexMap<Name, Zone>> for SubZoneConvert {
41    fn serialize_as<S: serde::Serializer>(
42        source: &IndexMap<Name, Zone>,
43        serializer: S,
44    ) -> Result<S::Ok, S::Error> {
45        source
46            .clone()
47            .into_iter()
48            .map(|x| (x.0, x.1.into()))
49            .collect::<IndexMap<Name, SubZone>>()
50            .serialize(serializer)
51    }
52}
53
54impl<'de> DeserializeAs<'de, IndexMap<Name, Zone>> for SubZoneConvert {
55    fn deserialize_as<D: serde::Deserializer<'de>>(
56        deserializer: D,
57    ) -> Result<IndexMap<Name, Zone>, D::Error> {
58        let from = IndexMap::<Name, SubZone>::deserialize(deserializer)?;
59        Ok(from.into_iter().map(|x| (x.0, x.1.into())).collect())
60    }
61}
62
63fn serde_true() -> bool {
64    true
65}
66
67fn serde_is_true(value: &bool) -> bool {
68    *value
69}
70
71#[serde_as]
72#[derive(Default, Serialize, Deserialize, Debug, Clone)]
73pub struct Zone {
74    //todo: some kind of indexmap structure?
75    #[serde_as(as = "VecRecordConvert")]
76    #[serde(default)]
77    pub records: Vec<Record>,
78    #[serde(default)]
79    #[serde_as(as = "SubZoneConvert")]
80    pub zones: IndexMap<Name, Zone>,
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub soa: Option<SoaData>,
83    #[serde(default, skip_serializing_if = "Vec::is_empty")]
84    pub nameservers: Vec<Name>,
85    #[serde(default)]
86    pub tsig_keys: IndexMap<String, TsigKey>,
87    #[serde(default = "serde_true", skip_serializing_if = "serde_is_true")]
88    pub authoritative: bool,
89    #[serde(skip)]
90    pub class: Class,
91    #[serde(default)]
92    pub allow_md5_tsig: bool,
93}
94
95#[serde_as]
96#[derive(Default, Serialize, Deserialize, Debug, Clone)]
97pub struct SubZone {
98    //todo: some kind of indexmap structure?
99    #[serde_as(as = "VecRecordConvert")]
100    #[serde(default)]
101    pub records: Vec<Record>,
102    #[serde(default = "serde_true", skip_serializing_if = "serde_is_true")]
103    pub authoritative: bool,
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub soa: Option<SoaData>,
106    #[serde(default, skip_serializing_if = "Vec::is_empty")]
107    pub nameservers: Vec<Name>,
108}
109
110impl From<SubZone> for Zone {
111    fn from(value: SubZone) -> Self {
112        Zone {
113            records: value.records,
114            zones: Default::default(),
115            tsig_keys: Default::default(),
116            authoritative: value.authoritative,
117            class: Default::default(),
118            allow_md5_tsig: Default::default(),
119            soa: value.soa,
120            nameservers: value.nameservers,
121        }
122    }
123}
124
125impl From<Zone> for SubZone {
126    fn from(value: Zone) -> Self {
127        SubZone {
128            records: value.records,
129            authoritative: value.authoritative,
130            soa: value.soa,
131            nameservers: value.nameservers,
132        }
133    }
134}
135
136#[serde_as]
137#[derive(Serialize, Deserialize, Clone, Debug)]
138pub struct TsigKey(#[serde_as(as = "serde_with::base64::Base64")] pub Vec<u8>);
139
140#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
141pub enum AnswerState {
142    None,
143    DomainSeen,
144}
145
146#[derive(Default, Serialize, Deserialize, Debug, Clone)]
147pub struct ZoneAnswer {
148    pub is_authoritative: bool,
149    pub answers: Vec<Record>,
150}
151
152impl Zone {
153    pub fn merge_from(&mut self, other: Zone) {
154        for record in other.records {
155            ZoneUpdateAction::AddRecord(record).apply_to(&Name::default(), self);
156        }
157        for (zone_name, new_zone) in other.zones {
158            match self.zones.entry(zone_name.clone()) {
159                Entry::Occupied(mut current_zone) => {
160                    let current_zone = current_zone.get_mut();
161                    for record in new_zone.records {
162                        ZoneUpdateAction::AddRecord(record).apply_to(&zone_name, current_zone);
163                    }
164                }
165                Entry::Vacant(v) => {
166                    v.insert(new_zone);
167                }
168            }
169        }
170    }
171
172    pub fn answer(
173        &self,
174        parent_zone: Option<&Zone>,
175        zone_name: &Name,
176        question: &Question,
177        response: &mut ZoneAnswer,
178        is_internal_soa: bool,
179    ) -> AnswerState {
180        response.is_authoritative = self.authoritative;
181        if &question.name == zone_name && question.type_ == Type::NS {
182            #[allow(clippy::unnecessary_unwrap)]
183            let nameservers = if self.nameservers.is_empty() && parent_zone.is_some() {
184                &parent_zone.unwrap().nameservers
185            } else {
186                &self.nameservers
187            };
188            for nameserver in nameservers {
189                response.answers.push(Record::new(
190                    zone_name.clone(),
191                    3600,
192                    TypeData::NS(nameserver.clone()),
193                ));
194            }
195            return AnswerState::DomainSeen;
196        }
197        if question.type_ == Type::SOA
198            && (&question.name == zone_name
199                || (is_internal_soa
200                    && self
201                        .zones
202                        .iter()
203                        .all(|(name, _)| !question.name.ends_with(name))))
204        {
205            if let Some(soa) = self
206                .soa
207                .clone()
208                .or_else(|| parent_zone.and_then(|x| x.soa.clone()))
209            {
210                response
211                    .answers
212                    .push(Record::new(zone_name.clone(), 60, TypeData::SOA(soa)));
213            } else {
214                warn!("no SOA specified for zone {}", zone_name);
215            }
216            return AnswerState::DomainSeen;
217        }
218        let mut state = AnswerState::None;
219        for record in &self.records {
220            if !record.name.contains(&question.name) {
221                continue;
222            }
223            state = AnswerState::DomainSeen;
224            if !question.type_.wants_by_query(record.type_) {
225                continue;
226            }
227            response.answers.push(Record {
228                name: question.name.clone(),
229                type_: record.type_,
230                class: record.class,
231                ttl: record.ttl,
232                data: record.data.clone(),
233            });
234        }
235        for (name, zone) in &self.zones {
236            if !question.name.ends_with(name) {
237                continue;
238            }
239            let substate = zone.answer(Some(self), name, question, response, is_internal_soa);
240            if substate > state {
241                state = substate;
242            }
243        }
244        state
245    }
246}
247
248fn default_ttl() -> u32 {
249    300
250}
251
252fn is_default_ttl(value: &u32) -> bool {
253    *value == 300
254}
255
256fn is_default_class(class: &Class) -> bool {
257    *class == Class::IN
258}
259
260#[derive(Serialize, Deserialize, Debug, Clone)]
261struct ZoneRecord {
262    domain: Name,
263    #[serde(rename = "type")]
264    type_: Type,
265    #[serde(default, skip_serializing_if = "is_default_class")]
266    class: Class,
267    #[serde(default = "default_ttl", skip_serializing_if = "is_default_ttl")]
268    ttl: u32,
269    data: String,
270}
271
272impl TryInto<Record> for ZoneRecord {
273    type Error = TypeDataParseError;
274
275    fn try_into(self) -> Result<Record, Self::Error> {
276        Ok(Record {
277            name: self.domain,
278            type_: self.type_,
279            class: self.class,
280            ttl: self.ttl,
281            data: TypeData::parse_str(self.type_, &self.data)?,
282        })
283    }
284}
285
286impl From<Record> for ZoneRecord {
287    fn from(value: Record) -> Self {
288        ZoneRecord {
289            domain: value.name.clone(),
290            type_: value.type_,
291            class: value.class,
292            ttl: value.ttl,
293            data: value.data.to_string(),
294        }
295    }
296}