1use std::{
11 ops::{Deref, DerefMut},
12 path::{Path, PathBuf},
13};
14
15#[cfg(feature = "metrics")]
16use crate::metrics::PersistentStoreMetrics;
17#[cfg(feature = "__dnssec")]
18use crate::{
19 dnssec::NxProofKind,
20 proto::dnssec::{DnsSecResult, DnssecSigner},
21 zone_handler::{DnssecZoneHandler, Nsec3QueryInfo},
22};
23use crate::{
24 proto::rr::{LowerName, Name, RecordType},
25 server::{Request, RequestInfo},
26 store::in_memory::{InMemoryZoneHandler, zone_from_path},
27 zone_handler::{
28 AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler,
29 ZoneTransfer, ZoneType,
30 },
31};
32use hickory_proto::rr::TSigResponseContext;
33use serde::Deserialize;
34
35pub struct FileZoneHandler {
40 in_memory: InMemoryZoneHandler,
41 #[cfg(feature = "metrics")]
42 #[allow(unused)]
43 metrics: PersistentStoreMetrics,
44}
45
46impl FileZoneHandler {
47 pub async fn new(in_memory: InMemoryZoneHandler) -> Self {
62 Self {
63 #[cfg(feature = "metrics")]
64 metrics: {
65 let new = PersistentStoreMetrics::new("file");
66 let records = in_memory.records().await;
67 new.zone_records.increment(records.len() as f64);
68 new
69 },
70 in_memory,
71 }
72 }
73
74 pub fn try_from_config(
76 origin: Name,
77 zone_type: ZoneType,
78 axfr_policy: AxfrPolicy,
79 root_dir: Option<&Path>,
80 config: &FileConfig,
81 #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
82 ) -> Result<Self, String> {
83 let zone_path = rooted(&config.zone_path, root_dir);
84 let records = zone_from_path(&zone_path, origin.clone())
85 .map_err(|e| format!("failed to load zone file: {e}"))?;
86
87 Ok(Self {
89 #[cfg(feature = "metrics")]
90 metrics: {
91 let new = PersistentStoreMetrics::new("file");
92 new.zone_records.increment(records.len() as f64);
93 new
94 },
95 in_memory: InMemoryZoneHandler::new(
96 origin,
97 records,
98 zone_type,
99 axfr_policy,
100 #[cfg(feature = "__dnssec")]
101 nx_proof_kind,
102 )?,
103 })
104 }
105}
106
107impl Deref for FileZoneHandler {
108 type Target = InMemoryZoneHandler;
109
110 fn deref(&self) -> &Self::Target {
111 &self.in_memory
112 }
113}
114
115impl DerefMut for FileZoneHandler {
116 fn deref_mut(&mut self) -> &mut Self::Target {
117 &mut self.in_memory
118 }
119}
120
121#[async_trait::async_trait]
122impl ZoneHandler for FileZoneHandler {
123 fn zone_type(&self) -> ZoneType {
125 self.in_memory.zone_type()
126 }
127
128 fn axfr_policy(&self) -> AxfrPolicy {
130 self.in_memory.axfr_policy()
131 }
132
133 fn origin(&self) -> &LowerName {
135 self.in_memory.origin()
136 }
137
138 async fn lookup(
154 &self,
155 name: &LowerName,
156 rtype: RecordType,
157 request_info: Option<&RequestInfo<'_>>,
158 lookup_options: LookupOptions,
159 ) -> LookupControlFlow<AuthLookup> {
160 self.in_memory
161 .lookup(name, rtype, request_info, lookup_options)
162 .await
163 }
164
165 async fn search(
177 &self,
178 request: &Request,
179 lookup_options: LookupOptions,
180 ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
181 self.in_memory.search(request, lookup_options).await
182 }
183
184 async fn zone_transfer(
185 &self,
186 request: &Request,
187 lookup_options: LookupOptions,
188 now: u64,
189 ) -> Option<(
190 Result<ZoneTransfer, LookupError>,
191 Option<TSigResponseContext>,
192 )> {
193 self.in_memory
194 .zone_transfer(request, lookup_options, now)
195 .await
196 }
197
198 async fn nsec_records(
207 &self,
208 name: &LowerName,
209 lookup_options: LookupOptions,
210 ) -> LookupControlFlow<AuthLookup> {
211 self.in_memory.nsec_records(name, lookup_options).await
212 }
213
214 #[cfg(feature = "__dnssec")]
215 async fn nsec3_records(
216 &self,
217 info: Nsec3QueryInfo<'_>,
218 lookup_options: LookupOptions,
219 ) -> LookupControlFlow<AuthLookup> {
220 self.in_memory.nsec3_records(info, lookup_options).await
221 }
222
223 #[cfg(feature = "__dnssec")]
224 fn nx_proof_kind(&self) -> Option<&NxProofKind> {
225 self.in_memory.nx_proof_kind()
226 }
227
228 #[cfg(feature = "metrics")]
229 fn metrics_label(&self) -> &'static str {
230 "file"
231 }
232}
233
234#[cfg(feature = "__dnssec")]
235#[async_trait::async_trait]
236impl DnssecZoneHandler for FileZoneHandler {
237 async fn add_zone_signing_key(&self, signer: DnssecSigner) -> DnsSecResult<()> {
239 self.in_memory.add_zone_signing_key(signer).await
240 }
241
242 async fn secure_zone(&self) -> DnsSecResult<()> {
244 DnssecZoneHandler::secure_zone(&self.in_memory).await
245 }
246}
247
248#[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
250#[serde(deny_unknown_fields)]
251pub struct FileConfig {
252 pub zone_path: PathBuf,
254}
255
256pub(crate) fn rooted(zone_file: &Path, root_dir: Option<&Path>) -> PathBuf {
257 match root_dir {
258 Some(root) => root.join(zone_file),
259 None => zone_file.to_owned(),
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use std::str::FromStr;
266
267 use crate::proto::rr::{RData, rdata::A};
268
269 use futures_executor::block_on;
270 use test_support::subscribe;
271
272 use super::*;
273 use crate::zone_handler::ZoneType;
274
275 #[test]
276 fn test_load_zone() {
277 subscribe();
278
279 #[cfg(feature = "__dnssec")]
280 let config = FileConfig {
281 zone_path: PathBuf::from("../../tests/test-data/test_configs/dnssec/example.com.zone"),
282 };
283 #[cfg(not(feature = "__dnssec"))]
284 let config = FileConfig {
285 zone_path: PathBuf::from("../../tests/test-data/test_configs/example.com.zone"),
286 };
287 let handler = FileZoneHandler::try_from_config(
288 Name::from_str("example.com.").unwrap(),
289 ZoneType::Primary,
290 AxfrPolicy::Deny,
291 None,
292 &config,
293 #[cfg(feature = "__dnssec")]
294 Some(NxProofKind::Nsec),
295 )
296 .expect("failed to load file");
297
298 let lookup = block_on(ZoneHandler::lookup(
299 &handler,
300 &LowerName::from_str("www.example.com.").unwrap(),
301 RecordType::A,
302 None,
303 LookupOptions::default(),
304 ))
305 .expect("lookup failed");
306
307 match lookup
308 .into_iter()
309 .next()
310 .expect("A record not found in zone handler")
311 .data
312 {
313 RData::A(ip) => assert_eq!(A::new(127, 0, 0, 1), ip),
314 _ => panic!("wrong rdata type returned"),
315 }
316
317 let include_lookup = block_on(ZoneHandler::lookup(
318 &handler,
319 &LowerName::from_str("include.alias.example.com.").unwrap(),
320 RecordType::A,
321 None,
322 LookupOptions::default(),
323 ))
324 .expect("INCLUDE lookup failed");
325
326 match include_lookup
327 .into_iter()
328 .next()
329 .expect("A record not found in zone handler")
330 .data
331 {
332 RData::A(ip) => assert_eq!(A::new(127, 0, 0, 5), ip),
333 _ => panic!("wrong rdata type returned"),
334 }
335 }
336}