hickory_proto/rr/rdata/
naptr.rs

1// Copyright 2015-2023 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//! Dynamic Delegation Discovery System
9
10use std::fmt;
11
12#[cfg(feature = "serde-config")]
13use serde::{Deserialize, Serialize};
14
15use crate::{
16    error::{ProtoError, ProtoResult},
17    rr::{domain::Name, RData, RecordData, RecordType},
18    serialize::binary::*,
19};
20
21/// [RFC 3403 DDDS DNS Database, October 2002](https://tools.ietf.org/html/rfc3403#section-4)
22///
23/// ```text
24/// 4.1 Packet Format
25///
26///   The packet format of the NAPTR RR is given below.  The DNS type code
27///   for NAPTR is 35.
28///
29///      The packet format for the NAPTR record is as follows
30///                                       1  1  1  1  1  1
31///         0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
32///       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
33///       |                     ORDER                     |
34///       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
35///       |                   PREFERENCE                  |
36///       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
37///       /                     FLAGS                     /
38///       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
39///       /                   SERVICES                    /
40///       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
41///       /                    REGEXP                     /
42///       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
43///       /                  REPLACEMENT                  /
44///       /                                               /
45///       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
46///
47///   <character-string> and <domain-name> as used here are defined in RFC
48///   1035 [7].
49/// ```
50#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
51#[derive(Debug, PartialEq, Eq, Hash, Clone)]
52pub struct NAPTR {
53    order: u16,
54    preference: u16,
55    flags: Box<[u8]>,
56    services: Box<[u8]>,
57    regexp: Box<[u8]>,
58    replacement: Name,
59}
60
61impl NAPTR {
62    /// Constructs a new NAPTR record
63    ///
64    /// # Arguments
65    ///
66    /// * `order` - the order in which the NAPTR records MUST be processed in order to accurately represent the ordered list of Rules.
67    /// * `preference` - this field is equivalent to the Priority value in the DDDS Algorithm.
68    /// * `flags` - flags to control aspects of the rewriting and interpretation of the fields in the record.  Flags are single characters from the set A-Z and 0-9.
69    /// * `services` - the Service Parameters applicable to this this delegation path.
70    /// * `regexp` - substitution expression that is applied to the original string held by the client in order to construct the next domain name to lookup.
71    /// * `replacement` - the next domain-name to query for depending on the potential values found in the flags field.
72    pub fn new(
73        order: u16,
74        preference: u16,
75        flags: Box<[u8]>,
76        services: Box<[u8]>,
77        regexp: Box<[u8]>,
78        replacement: Name,
79    ) -> Self {
80        Self {
81            order,
82            preference,
83            flags,
84            services,
85            regexp,
86            replacement,
87        }
88    }
89
90    /// ```text
91    ///   ORDER
92    ///      A 16-bit unsigned integer specifying the order in which the NAPTR
93    ///      records MUST be processed in order to accurately represent the
94    ///      ordered list of Rules.  The ordering is from lowest to highest.
95    ///      If two records have the same order value then they are considered
96    ///      to be the same rule and should be selected based on the
97    ///      combination of the Preference values and Services offered.
98    /// ```
99    pub fn order(&self) -> u16 {
100        self.order
101    }
102
103    /// ```text
104    ///   PREFERENCE
105    ///      Although it is called "preference" in deference to DNS
106    ///      terminology, this field is equivalent to the Priority value in the
107    ///      DDDS Algorithm.  It is a 16-bit unsigned integer that specifies
108    ///      the order in which NAPTR records with equal Order values SHOULD be
109    ///      processed, low numbers being processed before high numbers.  This
110    ///      is similar to the preference field in an MX record, and is used so
111    ///      domain administrators can direct clients towards more capable
112    ///      hosts or lighter weight protocols.  A client MAY look at records
113    ///      with higher preference values if it has a good reason to do so
114    ///      such as not supporting some protocol or service very well.
115    ///
116    ///      The important difference between Order and Preference is that once
117    ///      a match is found the client MUST NOT consider records with a
118    ///      different Order but they MAY process records with the same Order
119    ///      but different Preferences.  The only exception to this is noted in
120    ///      the second important Note in the DDDS algorithm specification
121    ///      concerning allowing clients to use more complex Service
122    ///      determination between steps 3 and 4 in the algorithm.  Preference
123    ///      is used to give communicate a higher quality of service to rules
124    ///      that are considered the same from an authority standpoint but not
125    ///      from a simple load balancing standpoint.
126    ///
127    ///      It is important to note that DNS contains several load balancing
128    ///      mechanisms and if load balancing among otherwise equal services
129    ///      should be needed then methods such as SRV records or multiple A
130    ///      records should be utilized to accomplish load balancing.
131    /// ```
132    pub fn preference(&self) -> u16 {
133        self.preference
134    }
135
136    /// ```text
137    ///   FLAGS
138    ///      A <character-string> containing flags to control aspects of the
139    ///      rewriting and interpretation of the fields in the record.  Flags
140    ///      are single characters from the set A-Z and 0-9.  The case of the
141    ///      alphabetic characters is not significant.  The field can be empty.
142    ///
143    ///      It is up to the Application specifying how it is using this
144    ///      Database to define the Flags in this field.  It must define which
145    ///      ones are terminal and which ones are not.
146    /// ```
147    pub fn flags(&self) -> &[u8] {
148        &self.flags
149    }
150
151    /// ```text
152    ///   SERVICES
153    ///      A <character-string> that specifies the Service Parameters
154    ///      applicable to this this delegation path.  It is up to the
155    ///      Application Specification to specify the values found in this
156    ///      field.
157    /// ```
158    pub fn services(&self) -> &[u8] {
159        &self.services
160    }
161
162    /// ```text
163    ///   REGEXP
164    ///      A <character-string> containing a substitution expression that is
165    ///      applied to the original string held by the client in order to
166    ///      construct the next domain name to lookup.  See the DDDS Algorithm
167    ///      specification for the syntax of this field.
168    ///
169    ///      As stated in the DDDS algorithm, The regular expressions MUST NOT
170    ///      be used in a cumulative fashion, that is, they should only be
171    ///      applied to the original string held by the client, never to the
172    ///      domain name produced by a previous NAPTR rewrite.  The latter is
173    ///      tempting in some applications but experience has shown such use to
174    ///      be extremely fault sensitive, very error prone, and extremely
175    ///      difficult to debug.
176    /// ```
177    pub fn regexp(&self) -> &[u8] {
178        &self.regexp
179    }
180
181    /// ```text
182    ///   REPLACEMENT
183    ///      A <domain-name> which is the next domain-name to query for
184    ///      depending on the potential values found in the flags field.  This
185    ///      field is used when the regular expression is a simple replacement
186    ///      operation.  Any value in this field MUST be a fully qualified
187    ///      domain-name.  Name compression is not to be used for this field.
188    ///
189    ///      This field and the REGEXP field together make up the Substitution
190    ///      Expression in the DDDS Algorithm.  It is simply a historical
191    ///      optimization specifically for DNS compression that this field
192    ///      exists.  The fields are also mutually exclusive.  If a record is
193    ///      returned that has values for both fields then it is considered to
194    ///      be in error and SHOULD be either ignored or an error returned.
195    /// ```
196    pub fn replacement(&self) -> &Name {
197        &self.replacement
198    }
199}
200
201/// verifies that the flags are valid
202pub fn verify_flags(flags: &[u8]) -> bool {
203    flags
204        .iter()
205        .all(|c| matches!(c, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z'))
206}
207
208impl BinEncodable for NAPTR {
209    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
210        self.order.emit(encoder)?;
211        self.preference.emit(encoder)?;
212        encoder.emit_character_data(&self.flags)?;
213        encoder.emit_character_data(&self.services)?;
214        encoder.emit_character_data(&self.regexp)?;
215
216        encoder.with_canonical_names(|encoder| self.replacement.emit(encoder))?;
217        Ok(())
218    }
219}
220
221impl<'r> BinDecodable<'r> for NAPTR {
222    fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult<Self> {
223        Ok(Self::new(
224            decoder.read_u16()?.unverified(/*any u16 is valid*/),
225            decoder.read_u16()?.unverified(/*any u16 is valid*/),
226            // must be 0-9a-z
227            decoder
228                .read_character_data()?
229                .verify_unwrap(|s| verify_flags(s))
230                .map_err(|_e| ProtoError::from("flags are not within range [a-zA-Z0-9]"))?
231                .to_vec()
232                .into_boxed_slice(),
233            decoder.read_character_data()?.unverified(/*any chardata*/).to_vec().into_boxed_slice(),
234            decoder.read_character_data()?.unverified(/*any chardata*/).to_vec().into_boxed_slice(),
235            Name::read(decoder)?,
236        ))
237    }
238}
239
240impl RecordData for NAPTR {
241    fn try_from_rdata(data: RData) -> Result<Self, RData> {
242        match data {
243            RData::NAPTR(csync) => Ok(csync),
244            _ => Err(data),
245        }
246    }
247
248    fn try_borrow(data: &RData) -> Option<&Self> {
249        match data {
250            RData::NAPTR(csync) => Some(csync),
251            _ => None,
252        }
253    }
254
255    fn record_type(&self) -> RecordType {
256        RecordType::NAPTR
257    }
258
259    fn into_rdata(self) -> RData {
260        RData::NAPTR(self)
261    }
262}
263
264/// [RFC 2915](https://tools.ietf.org/html/rfc2915), NAPTR DNS RR, September 2000
265///
266/// ```text
267/// Master File Format
268///
269///   The master file format follows the standard rules in RFC-1035 [1].
270///   Order and preference, being 16-bit unsigned integers, shall be an
271///   integer between 0 and 65535.  The Flags and Services and Regexp
272///   fields are all quoted <character-string>s.  Since the Regexp field
273///   can contain numerous backslashes and thus should be treated with
274///   care.  See Section 10 for how to correctly enter and escape the
275///   regular expression.
276///
277/// ;;      order pref flags service           regexp replacement
278/// IN NAPTR 100  50  "a"    "z3950+N2L+N2C"     ""   cidserver.example.com.
279/// IN NAPTR 100  50  "a"    "rcds+N2C"          ""   cidserver.example.com.
280/// IN NAPTR 100  50  "s"    "http+N2L+N2C+N2R"  ""   www.example.com.
281/// ```
282impl fmt::Display for NAPTR {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
284        write!(
285            f,
286            "{order} {pref} \"{flags}\" \"{service}\" \"{regexp}\" {replace}",
287            order = self.order,
288            pref = self.preference,
289            flags = &String::from_utf8_lossy(&self.flags),
290            service = &String::from_utf8_lossy(&self.services),
291            regexp = &String::from_utf8_lossy(&self.regexp),
292            replace = self.replacement
293        )
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    #![allow(clippy::dbg_macro, clippy::print_stdout)]
300
301    use super::*;
302    #[test]
303    fn test() {
304        use std::str::FromStr;
305
306        let rdata = NAPTR::new(
307            8,
308            16,
309            b"aa11AA".to_vec().into_boxed_slice(),
310            b"services".to_vec().into_boxed_slice(),
311            b"regexpr".to_vec().into_boxed_slice(),
312            Name::from_str("naptr.example.com").unwrap(),
313        );
314
315        let mut bytes = Vec::new();
316        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
317        assert!(rdata.emit(&mut encoder).is_ok());
318        let bytes = encoder.into_bytes();
319
320        println!("bytes: {bytes:?}");
321
322        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
323        let read_rdata = NAPTR::read(&mut decoder).expect("Decoding error");
324        assert_eq!(rdata, read_rdata);
325    }
326
327    #[test]
328    fn test_bad_data() {
329        use std::str::FromStr;
330
331        let rdata = NAPTR::new(
332            8,
333            16,
334            b"aa11AA-".to_vec().into_boxed_slice(),
335            b"services".to_vec().into_boxed_slice(),
336            b"regexpr".to_vec().into_boxed_slice(),
337            Name::from_str("naptr.example.com").unwrap(),
338        );
339
340        let mut bytes = Vec::new();
341        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
342        assert!(rdata.emit(&mut encoder).is_ok());
343        let bytes = encoder.into_bytes();
344
345        println!("bytes: {bytes:?}");
346
347        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
348        let read_rdata = NAPTR::read(&mut decoder);
349        assert!(
350            read_rdata.is_err(),
351            "should have failed decoding with bad flag data"
352        );
353    }
354}