Skip to main content

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