trust_dns_proto/rr/rdata/
naptr.rs

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