Commit
+57 -32 +/-8 browse
1 | diff --git a/src/common/auth_results.rs b/src/common/auth_results.rs |
2 | index dcfaa4d..711ad5d 100644 |
3 | --- a/src/common/auth_results.rs |
4 | +++ b/src/common/auth_results.rs |
5 | @@ -280,7 +280,7 @@ impl AsAuthResult for Error { |
6 | Error::RevokedPublicKey => "revoked public key", |
7 | Error::IncompatibleAlgorithms => "incompatible record/signature algorithms", |
8 | Error::SignatureExpired => "signature error", |
9 | - Error::DNSError => "dns error", |
10 | + Error::DNSError(_) => "dns error", |
11 | Error::DNSRecordNotFound(_) => "dns record not found", |
12 | Error::ARCInvalidInstance(i) => { |
13 | write!(header, "invalid ARC instance {})", i).ok(); |
14 | @@ -347,7 +347,7 @@ mod test { |
15 | "header.s=otherselctor header.b=YWJjZGVm header.from=jdoe@example.org" |
16 | ), |
17 | DkimOutput { |
18 | - result: DkimResult::TempError(Error::DNSError), |
19 | + result: DkimResult::TempError(Error::DNSError("".to_string())), |
20 | signature: (&Signature { |
21 | d: "atps.example.org".into(), |
22 | s: "otherselctor".into(), |
23 | diff --git a/src/common/resolver.rs b/src/common/resolver.rs |
24 | index b9e407a..bdc6829 100644 |
25 | --- a/src/common/resolver.rs |
26 | +++ b/src/common/resolver.rs |
27 | @@ -155,18 +155,24 @@ impl Resolver { |
28 | } |
29 | |
30 | let mx_lookup = self.resolver.mx_lookup(key.as_ref()).await?; |
31 | - let mut records = mx_lookup |
32 | - .as_lookup() |
33 | - .record_iter() |
34 | - .filter_map(|r| { |
35 | - let mx = r.data()?.as_mx()?; |
36 | - MX { |
37 | - exchange: mx.exchange().to_lowercase().to_string(), |
38 | - preference: mx.preference(), |
39 | + let mx_records = mx_lookup.as_lookup().records(); |
40 | + let mut records: Vec<MX> = Vec::with_capacity(mx_records.len()); |
41 | + for mx_record in mx_records { |
42 | + if let Some(mx) = mx_record.data().and_then(|r| r.as_mx()) { |
43 | + let preference = mx.preference(); |
44 | + let exchange = mx.exchange().to_lowercase().to_string(); |
45 | + |
46 | + if let Some(record) = records.iter_mut().find(|r| r.preference == preference) { |
47 | + record.exchanges.push(exchange); |
48 | + } else { |
49 | + records.push(MX { |
50 | + exchanges: vec![exchange], |
51 | + preference, |
52 | + }); |
53 | } |
54 | - .into() |
55 | - }) |
56 | - .collect::<Vec<_>>(); |
57 | + } |
58 | + } |
59 | + |
60 | records.sort_unstable_by(|a, b| a.preference.cmp(&b.preference)); |
61 | |
62 | Ok(self |
63 | @@ -226,6 +232,17 @@ impl Resolver { |
64 | .insert(key.into_owned(), Arc::new(ips), ipv6_lookup.valid_until())) |
65 | } |
66 | |
67 | + pub async fn ip_lookup( |
68 | + &self, |
69 | + key: impl IntoFqdn<'_>, |
70 | + ) -> crate::Result<impl Iterator<Item = IpAddr>> { |
71 | + self.resolver |
72 | + .lookup_ip(key.into_fqdn().as_ref()) |
73 | + .await |
74 | + .map(|lookup| lookup.into_iter()) |
75 | + .map_err(Error::from) |
76 | + } |
77 | + |
78 | pub async fn ptr_lookup<'x>(&self, addr: IpAddr) -> crate::Result<Arc<Vec<String>>> { |
79 | if let Some(value) = self.cache_ptr.get(&addr) { |
80 | return Ok(value); |
81 | @@ -274,7 +291,7 @@ impl Resolver { |
82 | if matches!(err.kind(), ResolveErrorKind::NoRecordsFound { .. }) { |
83 | Ok(false) |
84 | } else { |
85 | - Err(Error::DNSError) |
86 | + Err(err.into()) |
87 | } |
88 | } |
89 | } |
90 | @@ -341,7 +358,7 @@ impl From<ResolveError> for Error { |
91 | ResolveErrorKind::NoRecordsFound { response_code, .. } => { |
92 | Error::DNSRecordNotFound(*response_code) |
93 | } |
94 | - _ => Error::DNSError, |
95 | + _ => Error::DNSError(err.to_string()), |
96 | } |
97 | } |
98 | } |
99 | @@ -479,6 +496,16 @@ impl<'x> IntoFqdn<'x> for &'x str { |
100 | } |
101 | } |
102 | |
103 | + impl<'x> IntoFqdn<'x> for &String { |
104 | + fn into_fqdn(self) -> Cow<'x, str> { |
105 | + if self.ends_with('.') { |
106 | + self.to_lowercase().into() |
107 | + } else { |
108 | + format!("{}.", self.to_lowercase()).into() |
109 | + } |
110 | + } |
111 | + } |
112 | + |
113 | #[cfg(test)] |
114 | fn mock_resolve<T>(domain: &str) -> crate::Result<T> { |
115 | Err(if domain.contains("_parse_error.") { |
116 | @@ -486,7 +513,7 @@ fn mock_resolve<T>(domain: &str) -> crate::Result<T> { |
117 | } else if domain.contains("_invalid_record.") { |
118 | Error::InvalidRecordType |
119 | } else if domain.contains("_dns_error.") { |
120 | - Error::DNSError |
121 | + Error::DNSError("".to_string()) |
122 | } else { |
123 | Error::DNSRecordNotFound(trust_dns_resolver::proto::op::ResponseCode::NXDomain) |
124 | }) |
125 | diff --git a/src/dkim/mod.rs b/src/dkim/mod.rs |
126 | index 51cfd40..5708238 100644 |
127 | --- a/src/dkim/mod.rs |
128 | +++ b/src/dkim/mod.rs |
129 | @@ -197,7 +197,7 @@ impl<'x> DkimOutput<'x> { |
130 | } |
131 | |
132 | pub(crate) fn dns_error(err: Error) -> Self { |
133 | - if matches!(&err, Error::DNSError) { |
134 | + if matches!(&err, Error::DNSError(_)) { |
135 | DkimOutput::temp_err(err) |
136 | } else { |
137 | DkimOutput::perm_err(err) |
138 | @@ -239,7 +239,7 @@ impl<'x> ArcOutput<'x> { |
139 | |
140 | impl From<Error> for DkimResult { |
141 | fn from(err: Error) -> Self { |
142 | - if matches!(&err, Error::DNSError) { |
143 | + if matches!(&err, Error::DNSError(_)) { |
144 | DkimResult::TempError(err) |
145 | } else { |
146 | DkimResult::PermError(err) |
147 | diff --git a/src/dkim/verify.rs b/src/dkim/verify.rs |
148 | index 2cb335c..cb02c32 100644 |
149 | --- a/src/dkim/verify.rs |
150 | +++ b/src/dkim/verify.rs |
151 | @@ -219,7 +219,7 @@ impl Resolver { |
152 | | Error::UnsupportedKeyType |
153 | | Error::IncompatibleAlgorithms => (record.rr & RR_SIGNATURE) != 0, |
154 | Error::SignatureExpired => (record.rr & RR_EXPIRATION) != 0, |
155 | - Error::DNSError |
156 | + Error::DNSError(_) |
157 | | Error::DNSRecordNotFound(_) |
158 | | Error::InvalidRecordType |
159 | | Error::ParseError |
160 | diff --git a/src/dmarc/mod.rs b/src/dmarc/mod.rs |
161 | index 733ccc7..3a80ce5 100644 |
162 | --- a/src/dmarc/mod.rs |
163 | +++ b/src/dmarc/mod.rs |
164 | @@ -94,7 +94,7 @@ impl URI { |
165 | |
166 | impl From<Error> for DmarcResult { |
167 | fn from(err: Error) -> Self { |
168 | - if matches!(&err, Error::DNSError) { |
169 | + if matches!(&err, Error::DNSError(_)) { |
170 | DmarcResult::TempError(err) |
171 | } else { |
172 | DmarcResult::PermError(err) |
173 | diff --git a/src/dmarc/verify.rs b/src/dmarc/verify.rs |
174 | index c29d34c..6d3b35b 100644 |
175 | --- a/src/dmarc/verify.rs |
176 | +++ b/src/dmarc/verify.rs |
177 | @@ -137,7 +137,7 @@ impl Resolver { |
178 | .await |
179 | { |
180 | Ok(_) => true, |
181 | - Err(Error::DNSError) => return None, |
182 | + Err(Error::DNSError(_)) => return None, |
183 | _ => false, |
184 | } |
185 | { |
186 | diff --git a/src/lib.rs b/src/lib.rs |
187 | index 1025318..878e878 100644 |
188 | --- a/src/lib.rs |
189 | +++ b/src/lib.rs |
190 | @@ -301,8 +301,8 @@ pub(crate) enum Txt { |
191 | |
192 | #[derive(Debug, Clone, PartialEq, Eq)] |
193 | pub struct MX { |
194 | - exchange: String, |
195 | - preference: u16, |
196 | + pub exchanges: Vec<String>, |
197 | + pub preference: u16, |
198 | } |
199 | |
200 | #[derive(Debug, Clone)] |
201 | @@ -415,7 +415,7 @@ pub enum Error { |
202 | IncompatibleAlgorithms, |
203 | SignatureExpired, |
204 | |
205 | - DNSError, |
206 | + DNSError(String), |
207 | DNSRecordNotFound(ResponseCode), |
208 | |
209 | ARCChainTooLong, |
210 | @@ -467,7 +467,7 @@ impl Display for Error { |
211 | Error::ARCBrokenChain => write!(f, "Broken or missing ARC chain"), |
212 | Error::ARCChainTooLong => write!(f, "Too many ARC headers"), |
213 | Error::InvalidRecordType => write!(f, "Invalid record"), |
214 | - Error::DNSError => write!(f, "DNS resolution error"), |
215 | + Error::DNSError(err) => write!(f, "DNS resolution error: {}", err), |
216 | Error::DNSRecordNotFound(code) => write!(f, "DNS record not found: {}", code), |
217 | Error::DMARCNotAligned => write!(f, "DMARC policy not aligned"), |
218 | } |
219 | diff --git a/src/spf/verify.rs b/src/spf/verify.rs |
220 | index 6da76ed..a409a5a 100644 |
221 | --- a/src/spf/verify.rs |
222 | +++ b/src/spf/verify.rs |
223 | @@ -181,16 +181,14 @@ impl Resolver { |
224 | .await |
225 | { |
226 | Ok(records) => { |
227 | - for record in records.iter() { |
228 | + for exchange in records.iter().flat_map(|mx| mx.exchanges.iter()) { |
229 | if !lookup_limit.can_lookup() { |
230 | return output |
231 | .with_result(SpfResult::PermError) |
232 | .with_report(&spf_record); |
233 | } |
234 | |
235 | - match self |
236 | - .ip_matches(&record.exchange, ip, *ip4_mask, *ip6_mask) |
237 | - .await |
238 | + match self.ip_matches(exchange, ip, *ip4_mask, *ip6_mask).await |
239 | { |
240 | Ok(true) => { |
241 | matches = true; |
242 | @@ -385,12 +383,12 @@ impl Resolver { |
243 | ) -> crate::Result<bool> { |
244 | Ok(match ip { |
245 | IpAddr::V4(ip) => self |
246 | - .ipv4_lookup(target_name.as_ref()) |
247 | + .ipv4_lookup(target_name) |
248 | .await? |
249 | .iter() |
250 | .any(|addr| ip.matches_ipv4_mask(addr, ip4_mask)), |
251 | IpAddr::V6(ip) => self |
252 | - .ipv6_lookup(target_name.as_ref()) |
253 | + .ipv6_lookup(target_name) |
254 | .await? |
255 | .iter() |
256 | .any(|addr| ip.matches_ipv6_mask(addr, ip6_mask)), |
257 | @@ -620,7 +618,7 @@ mod test { |
258 | } |
259 | } |
260 | mxs.push(MX { |
261 | - exchange: mx_name, |
262 | + exchanges: vec![mx_name], |
263 | preference: (pos + 1) as u16, |
264 | }); |
265 | } |