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}