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 #[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 #[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}