hickory_server/authority/
authority_object.rs

1// Copyright 2015-2021 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! Object-safe authority and lookup traits
9
10use tracing::debug;
11
12#[cfg(feature = "__dnssec")]
13use crate::{authority::Nsec3QueryInfo, dnssec::NxProofKind, proto::dnssec::Proof};
14use crate::{
15    authority::{
16        Authority, LookupControlFlow, LookupOptions, MessageRequest, UpdateResult, ZoneType,
17    },
18    proto::rr::{LowerName, Record, RecordType},
19    server::RequestInfo,
20};
21
22/// An Object safe Authority
23#[async_trait::async_trait]
24pub trait AuthorityObject: Send + Sync {
25    /// What type is this zone
26    fn zone_type(&self) -> ZoneType;
27
28    /// Return true if AXFR is allowed
29    fn is_axfr_allowed(&self) -> bool;
30
31    /// Whether the authority can perform DNSSEC validation
32    fn can_validate_dnssec(&self) -> bool;
33
34    /// Perform a dynamic update of a zone
35    async fn update(&self, update: &MessageRequest) -> UpdateResult<bool>;
36
37    /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
38    fn origin(&self) -> &LowerName;
39
40    /// Looks up all Resource Records matching the given `Name` and `RecordType`.
41    ///
42    /// # Arguments
43    ///
44    /// * `name` - The name to look up.
45    /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
46    ///             `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
47    ///             due to the requirements that on zone transfers the `RecordType::SOA` must both
48    ///             precede and follow all other records.
49    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
50    ///                      algorithms, etc.)
51    ///
52    /// # Return value
53    ///
54    /// A LookupControlFlow containing the lookup that should be returned to the client.
55    async fn lookup(
56        &self,
57        name: &LowerName,
58        rtype: RecordType,
59        lookup_options: LookupOptions,
60    ) -> LookupControlFlow<Box<dyn LookupObject>>;
61
62    /// Consulting lookup for all Resource Records matching the given `Name` and `RecordType`.
63    /// This will be called in a chained authority configuration after an authority in the chain
64    /// has returned a lookup with a LookupControlFlow::Continue action. Every other authority in
65    /// the chain will be called via this consult method, until one either returns a
66    /// LookupControlFlow::Break action, or all authorities have been consulted.  The authority that
67    /// generated the primary lookup (the one returned via 'lookup') will not be consulted.
68    ///
69    /// # Arguments
70    ///
71    /// * `name` - The name to look up.
72    /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
73    ///             `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
74    ///             due to the requirements that on zone transfers the `RecordType::SOA` must both
75    ///             precede and follow all other records.
76    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
77    ///                      algorithms, etc.)
78    /// * `last_result` - The lookup returned by a previous authority in a chained configuration.
79    ///                   If a subsequent authority does not modify this lookup, it will be returned
80    ///                   to the client after consulting all authorities in the chain.
81    ///
82    /// # Return value
83    ///
84    /// A LookupControlFlow containing the lookup that should be returned to the client.  This can
85    /// be the same last_result that was passed in, or a new lookup, depending on the logic of the
86    /// authority in question.
87    async fn consult(
88        &self,
89        name: &LowerName,
90        rtype: RecordType,
91        lookup_options: LookupOptions,
92        last_result: LookupControlFlow<Box<dyn LookupObject>>,
93    ) -> LookupControlFlow<Box<dyn LookupObject>>;
94
95    /// Using the specified query, perform a lookup against this zone.
96    ///
97    /// # Arguments
98    ///
99    /// * `request_info` - the query to perform the lookup with.
100    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
101    ///                      algorithms, etc.)
102    ///
103    /// # Return value
104    ///
105    /// A LookupControlFlow containing the lookup that should be returned to the client.
106    async fn search(
107        &self,
108        request_info: RequestInfo<'_>,
109        lookup_options: LookupOptions,
110    ) -> LookupControlFlow<Box<dyn LookupObject>>;
111
112    /// Get the NS, NameServer, record for the zone
113    async fn ns(&self, lookup_options: LookupOptions) -> LookupControlFlow<Box<dyn LookupObject>> {
114        self.lookup(self.origin(), RecordType::NS, lookup_options)
115            .await
116    }
117
118    /// Return the NSEC records based on the given name
119    ///
120    /// # Arguments
121    ///
122    /// * `name` - given this name (i.e. the lookup name), return the NSEC record that is less than
123    ///            this
124    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
125    ///                      algorithms, etc.)
126    async fn get_nsec_records(
127        &self,
128        name: &LowerName,
129        lookup_options: LookupOptions,
130    ) -> LookupControlFlow<Box<dyn LookupObject>>;
131
132    /// Return the NSEC3 records based on the given query information.
133    #[cfg(feature = "__dnssec")]
134    async fn get_nsec3_records(
135        &self,
136        info: Nsec3QueryInfo<'_>,
137        lookup_options: LookupOptions,
138    ) -> LookupControlFlow<Box<dyn LookupObject>>;
139
140    /// Returns the SOA of the authority.
141    ///
142    /// *Note*: This will only return the SOA, if this is fulfilling a request, a standard lookup
143    ///  should be used, see `soa_secure()`, which will optionally return RRSIGs.
144    async fn soa(&self) -> LookupControlFlow<Box<dyn LookupObject>> {
145        // SOA should be origin|SOA
146        self.lookup(self.origin(), RecordType::SOA, LookupOptions::default())
147            .await
148    }
149
150    /// Returns the SOA record for the zone
151    async fn soa_secure(
152        &self,
153        lookup_options: LookupOptions,
154    ) -> LookupControlFlow<Box<dyn LookupObject>> {
155        self.lookup(self.origin(), RecordType::SOA, lookup_options)
156            .await
157    }
158
159    /// Returns the kind of non-existence proof used for this zone.
160    #[cfg(feature = "__dnssec")]
161    fn nx_proof_kind(&self) -> Option<&NxProofKind>;
162}
163
164#[async_trait::async_trait]
165impl<A, L> AuthorityObject for A
166where
167    A: Authority<Lookup = L> + Send + Sync + 'static,
168    L: LookupObject + Send + Sync + 'static,
169{
170    /// What type is this zone
171    fn zone_type(&self) -> ZoneType {
172        Authority::zone_type(self)
173    }
174
175    /// Return true if AXFR is allowed
176    fn is_axfr_allowed(&self) -> bool {
177        Authority::is_axfr_allowed(self)
178    }
179
180    /// Whether the authority can perform DNSSEC validation
181    fn can_validate_dnssec(&self) -> bool {
182        Authority::can_validate_dnssec(self)
183    }
184
185    /// Perform a dynamic update of a zone
186    async fn update(&self, update: &MessageRequest) -> UpdateResult<bool> {
187        Authority::update(self, update).await
188    }
189
190    /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
191    fn origin(&self) -> &LowerName {
192        Authority::origin(self)
193    }
194
195    /// Looks up all Resource Records matching the given `Name` and `RecordType`.
196    ///
197    /// # Arguments
198    ///
199    /// * `name` - The name to look up.
200    /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
201    ///             `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
202    ///             due to the requirements that on zone transfers the `RecordType::SOA` must both
203    ///             precede and follow all other records.
204    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
205    ///                      algorithms, etc.)
206    ///
207    /// # Return value
208    ///
209    /// A LookupControlFlow containing the lookup that should be returned to the client.
210    async fn lookup(
211        &self,
212        name: &LowerName,
213        rtype: RecordType,
214        lookup_options: LookupOptions,
215    ) -> LookupControlFlow<Box<dyn LookupObject>> {
216        Authority::lookup(self, name, rtype, lookup_options)
217            .await
218            .map_dyn()
219    }
220
221    /// Consulting lookup for all Resource Records matching the given `Name` and `RecordType`.
222    /// This will be called in a chained authority configuration after an authority in the chain
223    /// has returned a lookup with a LookupControlFlow::Continue action. Every other authority in
224    /// the chain will be called via this consult method, until one either returns a
225    /// LookupControlFlow::Break action, or all authorities have been consulted.  The authority that
226    /// generated the primary lookup (the one returned via 'lookup') will not be consulted.
227    ///
228    /// # Arguments
229    ///
230    /// * `name` - The name to look up.
231    /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
232    ///             `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
233    ///             due to the requirements that on zone transfers the `RecordType::SOA` must both
234    ///             precede and follow all other records.
235    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
236    ///                      algorithms, etc.)
237    /// * `last_result` - The lookup returned by a previous authority in a chained configuration.
238    ///                   If a subsequent authority does not modify this lookup, it will be returned
239    ///                   to the client after consulting all authorities in the chain.
240    ///
241    /// # Return value
242    ///
243    /// A LookupControlFlow containing the lookup that should be returned to the client.  This can
244    /// be the same last_result that was passed in, or a new lookup, depending on the logic of the
245    /// authority in question.
246    async fn consult(
247        &self,
248        name: &LowerName,
249        rtype: RecordType,
250        lookup_options: LookupOptions,
251        last_result: LookupControlFlow<Box<dyn LookupObject>>,
252    ) -> LookupControlFlow<Box<dyn LookupObject>> {
253        Authority::consult(self, name, rtype, lookup_options, last_result).await
254    }
255
256    /// Using the specified query, perform a lookup against this zone.
257    ///
258    /// # Arguments
259    ///
260    /// * `request_info` - the query to perform the lookup with.
261    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
262    ///                      algorithms, etc.)
263    ///
264    /// # Return value
265    ///
266    /// A LookupControlFlow containing the lookup that should be returned to the client.
267    async fn search(
268        &self,
269        request_info: RequestInfo<'_>,
270        lookup_options: LookupOptions,
271    ) -> LookupControlFlow<Box<dyn LookupObject>> {
272        debug!("performing {} on {}", request_info.query, self.origin());
273        Authority::search(self, request_info, lookup_options)
274            .await
275            .map_dyn()
276    }
277
278    /// Return the NSEC records based on the given name
279    ///
280    /// # Arguments
281    ///
282    /// * `name` - given this name (i.e. the lookup name), return the NSEC record that is less than
283    ///            this
284    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
285    ///                      algorithms, etc.)
286    async fn get_nsec_records(
287        &self,
288        name: &LowerName,
289        lookup_options: LookupOptions,
290    ) -> LookupControlFlow<Box<dyn LookupObject>> {
291        Authority::get_nsec_records(self, name, lookup_options)
292            .await
293            .map_dyn()
294    }
295
296    /// Return the NSEC3 records based on the given query information.
297    #[cfg(feature = "__dnssec")]
298    async fn get_nsec3_records(
299        &self,
300        info: Nsec3QueryInfo<'_>,
301        lookup_options: LookupOptions,
302    ) -> LookupControlFlow<Box<dyn LookupObject>> {
303        Authority::get_nsec3_records(self, info, lookup_options)
304            .await
305            .map_dyn()
306    }
307
308    /// Returns the kind of non-existence proof used for this zone.
309    #[cfg(feature = "__dnssec")]
310    fn nx_proof_kind(&self) -> Option<&NxProofKind> {
311        Authority::nx_proof_kind(self)
312    }
313}
314
315/// DNSSEC status of an answer
316#[derive(Clone, Copy, Debug)]
317pub enum DnssecSummary {
318    /// All records have been DNSSEC validated
319    Secure,
320    /// At least one record is in the Bogus state
321    Bogus,
322    /// Insecure / Indeterminate (e.g. "Island of security")
323    Insecure,
324}
325
326/// An Object Safe Lookup for Authority
327pub trait LookupObject: Send {
328    /// Returns true if either the associated Records are empty, or this is a NameExists or NxDomain
329    fn is_empty(&self) -> bool;
330
331    /// Conversion to an iterator
332    fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a>;
333
334    /// For CNAME and similar records, this is an additional set of lookup records
335    ///
336    /// it is acceptable for this to return None after the first call.
337    fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>>;
338
339    /// Whether the records have been DNSSEC validated or not
340    #[cfg(feature = "__dnssec")]
341    fn dnssec_summary(&self) -> DnssecSummary {
342        let mut all_secure = None;
343        for record in self.iter() {
344            match record.proof() {
345                Proof::Secure => {
346                    all_secure.get_or_insert(true);
347                }
348                Proof::Bogus => return DnssecSummary::Bogus,
349                _ => all_secure = Some(false),
350            }
351        }
352
353        if all_secure.unwrap_or(false) {
354            DnssecSummary::Secure
355        } else {
356            DnssecSummary::Insecure
357        }
358    }
359
360    /// Whether the records have been DNSSEC validated or not
361    #[cfg(not(feature = "__dnssec"))]
362    fn dnssec_summary(&self) -> DnssecSummary {
363        DnssecSummary::Insecure
364    }
365}
366
367/// A lookup that returns no records
368#[derive(Clone, Copy, Debug)]
369pub struct EmptyLookup;
370
371impl LookupObject for EmptyLookup {
372    fn is_empty(&self) -> bool {
373        true
374    }
375
376    fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a> {
377        Box::new([].iter())
378    }
379
380    fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>> {
381        None
382    }
383}