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    ) -> AnswerState {
179        response.is_authoritative = self.authoritative;
180        if &question.name == zone_name {
181            match question.type_ {
182                Type::SOA => {
183                    if let Some(soa) = self
184                        .soa
185                        .clone()
186                        .or_else(|| parent_zone.and_then(|x| x.soa.clone()))
187                    {
188                        response
189                            .answers
190                            .push(Record::new(zone_name, 0, TypeData::SOA(soa)));
191                    } else {
192                        warn!("no SOA specified for zone {}", zone_name);
193                    }
194                    return AnswerState::DomainSeen;
195                }
196                Type::NS => {
197                    #[allow(clippy::unnecessary_unwrap)]
198                    let nameservers = if self.nameservers.is_empty() && parent_zone.is_some() {
199                        &parent_zone.unwrap().nameservers
200                    } else {
201                        &self.nameservers
202                    };
203                    for nameserver in nameservers {
204                        response.answers.push(Record::new(
205                            zone_name,
206                            3600,
207                            TypeData::NS(nameserver.clone()),
208                        ));
209                    }
210                    return AnswerState::DomainSeen;
211                }
212                _ => (),
213            }
214        }
215        let mut state = AnswerState::None;
216        for record in &self.records {
217            if !record.name.contains(&question.name) {
218                continue;
219            }
220            state = AnswerState::DomainSeen;
221            if !question.type_.wants_by_query(record.type_) {
222                continue;
223            }
224            response.answers.push(Record {
225                name: question.name.clone(),
226                type_: record.type_,
227                class: record.class,
228                ttl: record.ttl,
229                data: record.data.clone(),
230            });
231        }
232        for (name, zone) in &self.zones {
233            if !question.name.ends_with(name) {
234                continue;
235            }
236            let substate = zone.answer(Some(self), name, question, response);
237            if substate > state {
238                state = substate;
239            }
240        }
241        state
242    }
243}
244
245fn default_ttl() -> u32 {
246    300
247}
248
249fn is_default_ttl(value: &u32) -> bool {
250    *value == 300
251}
252
253fn is_default_class(class: &Class) -> bool {
254    *class == Class::IN
255}
256
257#[derive(Serialize, Deserialize, Debug, Clone)]
258struct ZoneRecord {
259    domain: Name,
260    #[serde(rename = "type")]
261    type_: Type,
262    #[serde(default, skip_serializing_if = "is_default_class")]
263    class: Class,
264    #[serde(default = "default_ttl", skip_serializing_if = "is_default_ttl")]
265    ttl: u32,
266    data: String,
267}
268
269impl TryInto<Record> for ZoneRecord {
270    type Error = TypeDataParseError;
271
272    fn try_into(self) -> Result<Record, Self::Error> {
273        Ok(Record {
274            name: self.domain,
275            type_: self.type_,
276            class: self.class,
277            ttl: self.ttl,
278            data: TypeData::parse_str(self.type_, &self.data)?,
279        })
280    }
281}
282
283impl From<Record> for ZoneRecord {
284    fn from(value: Record) -> Self {
285        ZoneRecord {
286            domain: value.name.clone(),
287            type_: value.type_,
288            class: value.class,
289            ttl: value.ttl,
290            data: value.data.to_string(),
291        }
292    }
293}