hickory_proto/rr/rdata/mx.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//! mail exchange, email, record
9
10use std::fmt;
11
12#[cfg(feature = "serde-config")]
13use serde::{Deserialize, Serialize};
14
15use crate::{
16 error::ProtoResult,
17 rr::{domain::Name, RData, RecordData, RecordType},
18 serialize::binary::*,
19};
20
21/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
22///
23/// ```text
24/// 3.3.9. MX RDATA format
25///
26/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
27/// | PREFERENCE |
28/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
29/// / EXCHANGE /
30/// / /
31/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
32///
33/// MX records cause type A additional section processing for the host
34/// specified by EXCHANGE. The use of MX RRs is explained in detail in
35/// [RFC-974].
36///
37/// ```
38#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
39#[derive(Debug, PartialEq, Eq, Hash, Clone)]
40pub struct MX {
41 preference: u16,
42 exchange: Name,
43}
44
45impl MX {
46 /// Constructs a new MX RData
47 ///
48 /// # Arguments
49 ///
50 /// * `preference` - weight of this MX record as opposed to others, lower values have the higher preference
51 /// * `exchange` - Name labels for the mail server
52 ///
53 /// # Returns
54 ///
55 /// A new MX RData for use in a Resource Record
56 pub fn new(preference: u16, exchange: Name) -> Self {
57 Self {
58 preference,
59 exchange,
60 }
61 }
62
63 /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
64 ///
65 /// ```text
66 /// PREFERENCE A 16 bit integer which specifies the preference given to
67 /// this RR among others at the same owner. Lower values
68 /// are preferred.
69 /// ```
70 pub fn preference(&self) -> u16 {
71 self.preference
72 }
73
74 /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
75 ///
76 /// ```text
77 /// EXCHANGE A <domain-name> which specifies a host willing to act as
78 /// a mail exchange for the owner name.
79 /// ```
80 pub fn exchange(&self) -> &Name {
81 &self.exchange
82 }
83}
84
85impl BinEncodable for MX {
86 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
87 let is_canonical_names = encoder.is_canonical_names();
88 encoder.emit_u16(self.preference())?;
89
90 // to_lowercase for rfc4034 and rfc6840
91 self.exchange()
92 .emit_with_lowercase(encoder, is_canonical_names)?;
93 Ok(())
94 }
95}
96
97impl<'r> BinDecodable<'r> for MX {
98 fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult<Self> {
99 Ok(Self::new(
100 decoder.read_u16()?.unverified(/*any u16 is valid*/),
101 Name::read(decoder)?,
102 ))
103 }
104}
105
106impl RecordData for MX {
107 fn try_from_rdata(data: RData) -> Result<Self, RData> {
108 match data {
109 RData::MX(csync) => Ok(csync),
110 _ => Err(data),
111 }
112 }
113
114 fn try_borrow(data: &RData) -> Option<&Self> {
115 match data {
116 RData::MX(csync) => Some(csync),
117 _ => None,
118 }
119 }
120
121 fn record_type(&self) -> RecordType {
122 RecordType::MX
123 }
124
125 fn into_rdata(self) -> RData {
126 RData::MX(self)
127 }
128}
129
130/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987
131///
132/// ```text
133/// MX (Mail Exchanger) (See RFC-974 for more details.)
134///
135/// <name> [<ttl>] [<class>] MX <preference> <host>
136///
137/// MX records specify where mail for a domain name should be delivered.
138/// There may be multiple MX records for a particular name. The
139/// preference value specifies the order a mailer should try multiple MX
140/// records when delivering mail. Zero is the highest preference.
141/// Multiple records for the same name may have the same preference.
142///
143/// A host BAR.FOO.COM may want its mail to be delivered to the host
144/// PO.FOO.COM and would then use the MX record:
145///
146/// BAR.FOO.COM. MX 10 PO.FOO.COM.
147///
148/// A host BAZ.FOO.COM may want its mail to be delivered to one of three
149/// different machines, in the following order:
150///
151/// BAZ.FOO.COM. MX 10 PO1.FOO.COM.
152/// MX 20 PO2.FOO.COM.
153/// MX 30 PO3.FOO.COM.
154///
155/// An entire domain of hosts not connected to the Internet may want
156/// their mail to go through a mail gateway that knows how to deliver
157/// mail to them. If they would like mail addressed to any host in the
158/// domain FOO.COM to go through the mail gateway they might use:
159///
160/// FOO.COM. MX 10 RELAY.CS.NET.
161/// *.FOO.COM. MX 20 RELAY.CS.NET.
162///
163/// Note that you can specify a wildcard in the MX record to match on
164/// anything in FOO.COM, but that it won't match a plain FOO.COM.
165impl fmt::Display for MX {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
167 write!(f, "{pref} {ex}", pref = self.preference, ex = self.exchange)
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 #![allow(clippy::dbg_macro, clippy::print_stdout)]
174
175 use super::*;
176
177 #[test]
178 fn test() {
179 use std::str::FromStr;
180
181 let rdata = MX::new(16, Name::from_str("mail.example.com").unwrap());
182
183 let mut bytes = Vec::new();
184 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
185 assert!(rdata.emit(&mut encoder).is_ok());
186 let bytes = encoder.into_bytes();
187
188 println!("bytes: {bytes:?}");
189
190 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
191 let read_rdata = MX::read(&mut decoder).expect("Decoding error");
192 assert_eq!(rdata, read_rdata);
193 }
194}