Commit
+619 -587 +/-36 browse
1 | diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml |
2 | index 5c2da0c..387b9c4 100644 |
3 | --- a/.github/workflows/rust.yml |
4 | +++ b/.github/workflows/rust.yml |
5 | @@ -15,8 +15,19 @@ jobs: |
6 | runs-on: ubuntu-latest |
7 | |
8 | steps: |
9 | - - uses: actions/checkout@v2 |
10 | - - name: Build |
11 | - run: cargo build --verbose |
12 | - - name: Run tests |
13 | - run: cargo test --verbose |
14 | + - name: Checkout |
15 | + uses: actions/checkout@v2 |
16 | + - name: cargo fmt -- --check |
17 | + uses: actions-rs/cargo@v1 |
18 | + with: |
19 | + command: fmt |
20 | + args: --all -- --check |
21 | + - run: rustup component add clippy |
22 | + - uses: actions-rs/clippy-check@v1 |
23 | + with: |
24 | + token: ${{ secrets.GITHUB_TOKEN }} |
25 | + args: --all-features |
26 | + - name: Build |
27 | + run: cargo build --verbose |
28 | + - name: Run tests |
29 | + run: cargo test --verbose |
30 | diff --git a/CHANGELOG.md b/CHANGELOG.md |
31 | index 173f38e..f72c9b4 100644 |
32 | --- a/CHANGELOG.md |
33 | +++ b/CHANGELOG.md |
34 | @@ -1,3 +1,8 @@ |
35 | + mail-auth 0.2.0 |
36 | + ================================ |
37 | + - Fixed: Acronyms in type names do not match the recommended spelling from RFC 430 (#31) |
38 | + - Fixed: Inconsistent use of '.' at the end of strings on fmt::Display impl for Error (#31) |
39 | + |
40 | mail-auth 0.1.0 |
41 | ================================ |
42 | - Initial release. |
43 | diff --git a/Cargo.toml b/Cargo.toml |
44 | index 051b5f9..7050854 100644 |
45 | --- a/Cargo.toml |
46 | +++ b/Cargo.toml |
47 | @@ -1,7 +1,7 @@ |
48 | [package] |
49 | name = "mail-auth" |
50 | description = "DKIM, ARC, SPF and DMARC library for Rust" |
51 | - version = "0.1.0" |
52 | + version = "0.2.0" |
53 | edition = "2021" |
54 | authors = [ "Stalwart Labs <hello@stalw.art>"] |
55 | license = "Apache-2.0 OR MIT" |
56 | diff --git a/README.md b/README.md |
57 | index abcaec8..ea051a6 100644 |
58 | --- a/README.md |
59 | +++ b/README.md |
60 | @@ -43,7 +43,7 @@ Features: |
61 | let result = resolver.verify_dkim(&authenticated_message).await; |
62 | |
63 | // Make sure all signatures passed verification |
64 | - assert!(result.iter().all(|s| s.result() == &DKIMResult::Pass)); |
65 | + assert!(result.iter().all(|s| s.result() == &DkimResult::Pass)); |
66 | ``` |
67 | |
68 | ### DKIM Signing |
69 | @@ -93,7 +93,7 @@ Features: |
70 | let result = resolver.verify_arc(&authenticated_message).await; |
71 | |
72 | // Make sure ARC passed verification |
73 | - assert_eq!(result.result(), &DKIMResult::Pass); |
74 | + assert_eq!(result.result(), &DkimResult::Pass); |
75 | ``` |
76 | |
77 | ### ARC Chain Sealing |
78 | @@ -118,7 +118,7 @@ Features: |
79 | if arc_result.can_be_sealed() { |
80 | // Seal the e-mail message using RSA-SHA256 |
81 | let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
82 | - let arc_set = ARC::new(&auth_results) |
83 | + let arc_set = ArcSet::new(&auth_results) |
84 | .domain("example.org") |
85 | .selector("default") |
86 | .headers(["From", "To", "Subject", "DKIM-Signature"]) |
87 | @@ -142,13 +142,13 @@ Features: |
88 | let result = resolver |
89 | .verify_spf_helo("127.0.0.1".parse().unwrap(), "gmail.com") |
90 | .await; |
91 | - assert_eq!(result.result(), SPFResult::Fail); |
92 | + assert_eq!(result.result(), SpfResult::Fail); |
93 | |
94 | // Verify MAIL-FROM identity |
95 | let result = resolver |
96 | .verify_spf_sender("::1".parse().unwrap(), "gmail.com", "sender@gmail.com") |
97 | .await; |
98 | - assert_eq!(result.result(), SPFResult::Fail); |
99 | + assert_eq!(result.result(), SpfResult::Fail); |
100 | ``` |
101 | |
102 | ### DMARC Policy Evaluation |
103 | @@ -175,8 +175,8 @@ Features: |
104 | &spf_result, |
105 | ) |
106 | .await; |
107 | - assert_eq!(dmarc_result.dkim_result(), &DMARCResult::Pass); |
108 | - assert_eq!(dmarc_result.spf_result(), &DMARCResult::Pass); |
109 | + assert_eq!(dmarc_result.dkim_result(), &DmarcResult::Pass); |
110 | + assert_eq!(dmarc_result.spf_result(), &DmarcResult::Pass); |
111 | ``` |
112 | |
113 | More examples available under the [examples](examples) directory. |
114 | @@ -192,7 +192,7 @@ To run the testsuite: |
115 | To fuzz the library with `cargo-fuzz`: |
116 | |
117 | ```bash |
118 | - $ cargo +nightly fuzz run mail_parser |
119 | + $ cargo +nightly fuzz run mail_auth |
120 | ``` |
121 | |
122 | ## Conformed RFCs |
123 | diff --git a/examples/arc_seal.rs b/examples/arc_seal.rs |
124 | index 7a93c9c..7364ede 100644 |
125 | --- a/examples/arc_seal.rs |
126 | +++ b/examples/arc_seal.rs |
127 | @@ -9,7 +9,7 @@ |
128 | */ |
129 | |
130 | use mail_auth::{ |
131 | - arc::ARC, common::headers::HeaderWriter, AuthenticatedMessage, AuthenticationResults, |
132 | + arc::ArcSet, common::headers::HeaderWriter, AuthenticatedMessage, AuthenticationResults, |
133 | PrivateKey, Resolver, |
134 | }; |
135 | |
136 | @@ -52,7 +52,7 @@ async fn main() { |
137 | if arc_result.can_be_sealed() { |
138 | // Seal the e-mail message using RSA-SHA256 |
139 | let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
140 | - let arc_set = ARC::new(&auth_results) |
141 | + let arc_set = ArcSet::new(&auth_results) |
142 | .domain("example.org") |
143 | .selector("default") |
144 | .headers(["From", "To", "Subject", "DKIM-Signature"]) |
145 | diff --git a/examples/arc_verify.rs b/examples/arc_verify.rs |
146 | index e04cf22..9b9e2a4 100644 |
147 | --- a/examples/arc_verify.rs |
148 | +++ b/examples/arc_verify.rs |
149 | @@ -8,7 +8,7 @@ |
150 | * except according to those terms. |
151 | */ |
152 | |
153 | - use mail_auth::{AuthenticatedMessage, DKIMResult, Resolver}; |
154 | + use mail_auth::{AuthenticatedMessage, DkimResult, Resolver}; |
155 | |
156 | const TEST_MESSAGE: &str = include_str!("../resources/arc/001.txt"); |
157 | |
158 | @@ -24,5 +24,5 @@ async fn main() { |
159 | let result = resolver.verify_arc(&authenticated_message).await; |
160 | |
161 | // Make sure ARC passed verification |
162 | - assert_eq!(result.result(), &DKIMResult::Pass); |
163 | + assert_eq!(result.result(), &DkimResult::Pass); |
164 | } |
165 | diff --git a/examples/dkim_verify.rs b/examples/dkim_verify.rs |
166 | index 622fec9..7aa98ad 100644 |
167 | --- a/examples/dkim_verify.rs |
168 | +++ b/examples/dkim_verify.rs |
169 | @@ -8,7 +8,7 @@ |
170 | * except according to those terms. |
171 | */ |
172 | |
173 | - use mail_auth::{AuthenticatedMessage, DKIMResult, Resolver}; |
174 | + use mail_auth::{AuthenticatedMessage, DkimResult, Resolver}; |
175 | |
176 | const TEST_MESSAGE: &str = r#"DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; |
177 | d=football.example.com; i=@football.example.com; |
178 | @@ -49,5 +49,5 @@ async fn main() { |
179 | let result = resolver.verify_dkim(&authenticated_message).await; |
180 | |
181 | // Make sure all signatures passed verification |
182 | - assert!(result.iter().all(|s| s.result() == &DKIMResult::Pass)); |
183 | + assert!(result.iter().all(|s| s.result() == &DkimResult::Pass)); |
184 | } |
185 | diff --git a/examples/dmarc_verify.rs b/examples/dmarc_verify.rs |
186 | index cbf1049..b43a7ec 100644 |
187 | --- a/examples/dmarc_verify.rs |
188 | +++ b/examples/dmarc_verify.rs |
189 | @@ -8,7 +8,7 @@ |
190 | * except according to those terms. |
191 | */ |
192 | |
193 | - use mail_auth::{AuthenticatedMessage, DMARCResult, Resolver}; |
194 | + use mail_auth::{AuthenticatedMessage, DmarcResult, Resolver}; |
195 | |
196 | const TEST_MESSAGE: &str = r#"DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; |
197 | d=football.example.com; i=@football.example.com; |
198 | @@ -60,6 +60,6 @@ async fn main() { |
199 | &spf_result, |
200 | ) |
201 | .await; |
202 | - assert_eq!(dmarc_result.dkim_result(), &DMARCResult::Pass); |
203 | - assert_eq!(dmarc_result.spf_result(), &DMARCResult::Pass); |
204 | + assert_eq!(dmarc_result.dkim_result(), &DmarcResult::Pass); |
205 | + assert_eq!(dmarc_result.spf_result(), &DmarcResult::Pass); |
206 | } |
207 | diff --git a/examples/report_dmarc_generate.rs b/examples/report_dmarc_generate.rs |
208 | index 8e1fcd4..b730eb8 100644 |
209 | --- a/examples/report_dmarc_generate.rs |
210 | +++ b/examples/report_dmarc_generate.rs |
211 | @@ -9,8 +9,8 @@ |
212 | */ |
213 | |
214 | use mail_auth::report::{ |
215 | - ActionDisposition, Alignment, DKIMAuthResult, DKIMResult, DMARCResult, Disposition, |
216 | - PolicyOverride, PolicyOverrideReason, Record, Report, SPFAuthResult, SPFDomainScope, SPFResult, |
217 | + ActionDisposition, Alignment, DKIMAuthResult, Disposition, DkimResult, DmarcResult, |
218 | + PolicyOverride, PolicyOverrideReason, Record, Report, SPFAuthResult, SPFDomainScope, SpfResult, |
219 | }; |
220 | |
221 | fn main() { |
222 | @@ -36,8 +36,8 @@ fn main() { |
223 | .with_source_ip("192.168.1.2".parse().unwrap()) |
224 | .with_count(3) |
225 | .with_action_disposition(ActionDisposition::Pass) |
226 | - .with_dmarc_dkim_result(DMARCResult::Pass) |
227 | - .with_dmarc_spf_result(DMARCResult::Fail) |
228 | + .with_dmarc_dkim_result(DmarcResult::Pass) |
229 | + .with_dmarc_spf_result(DmarcResult::Fail) |
230 | .with_policy_override_reason( |
231 | PolicyOverrideReason::new(PolicyOverride::Forwarded) |
232 | .with_comment("it was forwarded"), |
233 | @@ -53,14 +53,14 @@ fn main() { |
234 | DKIMAuthResult::new() |
235 | .with_domain("test.org") |
236 | .with_selector("my-selector") |
237 | - .with_result(DKIMResult::PermError) |
238 | + .with_result(DkimResult::PermError) |
239 | .with_human_result("failed to parse record"), |
240 | ) |
241 | .with_spf_auth_result( |
242 | SPFAuthResult::new() |
243 | .with_domain("test.org") |
244 | .with_scope(SPFDomainScope::Helo) |
245 | - .with_result(SPFResult::SoftFail) |
246 | + .with_result(SpfResult::SoftFail) |
247 | .with_human_result("dns timed out"), |
248 | ), |
249 | ) |
250 | @@ -69,8 +69,8 @@ fn main() { |
251 | .with_source_ip("a:b:c::e:f".parse().unwrap()) |
252 | .with_count(99) |
253 | .with_action_disposition(ActionDisposition::Reject) |
254 | - .with_dmarc_dkim_result(DMARCResult::Fail) |
255 | - .with_dmarc_spf_result(DMARCResult::Pass) |
256 | + .with_dmarc_dkim_result(DmarcResult::Fail) |
257 | + .with_dmarc_spf_result(DmarcResult::Pass) |
258 | .with_policy_override_reason( |
259 | PolicyOverrideReason::new(PolicyOverride::LocalPolicy) |
260 | .with_comment("on the white list"), |
261 | @@ -86,14 +86,14 @@ fn main() { |
262 | DKIMAuthResult::new() |
263 | .with_domain("test2.org") |
264 | .with_selector("my-other-selector") |
265 | - .with_result(DKIMResult::Neutral) |
266 | + .with_result(DkimResult::Neutral) |
267 | .with_human_result("something went wrong"), |
268 | ) |
269 | .with_spf_auth_result( |
270 | SPFAuthResult::new() |
271 | .with_domain("test.org") |
272 | .with_scope(SPFDomainScope::MailFrom) |
273 | - .with_result(SPFResult::None) |
274 | + .with_result(SpfResult::None) |
275 | .with_human_result("no policy found"), |
276 | ), |
277 | ) |
278 | diff --git a/examples/spf_verify.rs b/examples/spf_verify.rs |
279 | index 93930f7..f960b52 100644 |
280 | --- a/examples/spf_verify.rs |
281 | +++ b/examples/spf_verify.rs |
282 | @@ -8,7 +8,7 @@ |
283 | * except according to those terms. |
284 | */ |
285 | |
286 | - use mail_auth::{Resolver, SPFResult}; |
287 | + use mail_auth::{Resolver, SpfResult}; |
288 | |
289 | #[tokio::main] |
290 | async fn main() { |
291 | @@ -19,11 +19,11 @@ async fn main() { |
292 | let result = resolver |
293 | .verify_spf_helo("127.0.0.1".parse().unwrap(), "gmail.com") |
294 | .await; |
295 | - assert_eq!(result.result(), SPFResult::Fail); |
296 | + assert_eq!(result.result(), SpfResult::Fail); |
297 | |
298 | // Verify MAIL-FROM identity |
299 | let result = resolver |
300 | .verify_spf_sender("::1".parse().unwrap(), "gmail.com", "sender@gmail.com") |
301 | .await; |
302 | - assert_eq!(result.result(), SPFResult::Fail); |
303 | + assert_eq!(result.result(), SpfResult::Fail); |
304 | } |
305 | diff --git a/fuzz/fuzz_targets/mail_auth.rs b/fuzz/fuzz_targets/mail_auth.rs |
306 | index b4b36cf..3d5d520 100644 |
307 | --- a/fuzz/fuzz_targets/mail_auth.rs |
308 | +++ b/fuzz/fuzz_targets/mail_auth.rs |
309 | @@ -15,9 +15,9 @@ use mail_auth::{ |
310 | arc, |
311 | common::parse::TxtRecordParser, |
312 | dkim::{self, Atps, DomainKey, DomainKeyReport}, |
313 | - dmarc::DMARC, |
314 | + dmarc::Dmarc, |
315 | report::{Feedback, Report}, |
316 | - spf::{Macro, SPF}, |
317 | + spf::{Macro, Spf}, |
318 | AuthenticatedMessage, |
319 | }; |
320 | |
321 | @@ -53,11 +53,11 @@ fuzz_target!(|data: &[u8]| { |
322 | Atps::parse(data).ok(); |
323 | Atps::parse(&data_txt).ok(); |
324 | |
325 | - DMARC::parse(data).ok(); |
326 | - DMARC::parse(&data_txt).ok(); |
327 | + Dmarc::parse(data).ok(); |
328 | + Dmarc::parse(&data_txt).ok(); |
329 | |
330 | - SPF::parse(data).ok(); |
331 | - SPF::parse(&data_txt).ok(); |
332 | + Spf::parse(data).ok(); |
333 | + Spf::parse(&data_txt).ok(); |
334 | |
335 | Macro::parse(data).ok(); |
336 | Macro::parse(&data_txt).ok(); |
337 | diff --git a/src/arc/headers.rs b/src/arc/headers.rs |
338 | index 15c6515..381477b 100644 |
339 | --- a/src/arc/headers.rs |
340 | +++ b/src/arc/headers.rs |
341 | @@ -8,7 +8,7 @@ |
342 | * except according to those terms. |
343 | */ |
344 | |
345 | - use std::io::Write; |
346 | + use std::io; |
347 | |
348 | use crate::{ |
349 | common::headers::HeaderWriter, |
350 | @@ -16,10 +16,10 @@ use crate::{ |
351 | AuthenticationResults, |
352 | }; |
353 | |
354 | - use super::{ChainValidation, Seal, Signature, ARC}; |
355 | + use super::{ArcSet, ChainValidation, Seal, Signature}; |
356 | |
357 | impl<'x> Signature<'x> { |
358 | - pub(crate) fn write(&self, mut writer: impl Write, as_header: bool) -> std::io::Result<()> { |
359 | + pub(crate) fn write(&self, mut writer: impl io::Write, as_header: bool) -> io::Result<()> { |
360 | let (header, new_line) = match self.ch { |
361 | Canonicalization::Relaxed if !as_header => (&b"arc-message-signature:"[..], &b" "[..]), |
362 | _ => (&b"ARC-Message-Signature: "[..], &b"\r\n\t"[..]), |
363 | @@ -99,7 +99,7 @@ impl<'x> Signature<'x> { |
364 | } |
365 | |
366 | impl<'x> Seal<'x> { |
367 | - pub(crate) fn write(&self, mut writer: impl Write, as_header: bool) -> std::io::Result<()> { |
368 | + pub(crate) fn write(&self, mut writer: impl io::Write, as_header: bool) -> io::Result<()> { |
369 | let (header, new_line) = if !as_header { |
370 | (&b"arc-seal:"[..], &b" "[..]) |
371 | } else { |
372 | @@ -156,10 +156,10 @@ impl<'x> Seal<'x> { |
373 | impl<'x> AuthenticationResults<'x> { |
374 | pub(crate) fn write( |
375 | &self, |
376 | - mut writer: impl Write, |
377 | + mut writer: impl io::Write, |
378 | i: u32, |
379 | as_header: bool, |
380 | - ) -> std::io::Result<()> { |
381 | + ) -> io::Result<()> { |
382 | writer.write_all(if !as_header { |
383 | b"arc-authentication-results:" |
384 | } else { |
385 | @@ -189,8 +189,8 @@ impl<'x> AuthenticationResults<'x> { |
386 | } |
387 | } |
388 | |
389 | - impl<'x> HeaderWriter for ARC<'x> { |
390 | - fn write_header(&self, mut writer: impl Write) -> std::io::Result<()> { |
391 | + impl<'x> HeaderWriter for ArcSet<'x> { |
392 | + fn write_header(&self, mut writer: impl io::Write) -> io::Result<()> { |
393 | self.seal.write(&mut writer, true)?; |
394 | self.signature.write(&mut writer, true)?; |
395 | self.results.write(&mut writer, self.seal.i, true) |
396 | diff --git a/src/arc/mod.rs b/src/arc/mod.rs |
397 | index ed4485a..01326f0 100644 |
398 | --- a/src/arc/mod.rs |
399 | +++ b/src/arc/mod.rs |
400 | @@ -18,7 +18,7 @@ use std::borrow::Cow; |
401 | use crate::{ |
402 | common::{headers::Header, verify::VerifySignature}, |
403 | dkim::{Algorithm, Canonicalization}, |
404 | - ARCOutput, AuthenticationResults, DKIMResult, |
405 | + ArcOutput, AuthenticationResults, DkimResult, |
406 | }; |
407 | |
408 | #[derive(Debug, PartialEq, Eq, Clone, Default)] |
409 | @@ -55,7 +55,7 @@ pub struct Results { |
410 | } |
411 | |
412 | #[derive(Debug, Clone, PartialEq, Eq)] |
413 | - pub struct ARC<'x> { |
414 | + pub struct ArcSet<'x> { |
415 | pub(crate) signature: Signature<'x>, |
416 | pub(crate) seal: Seal<'x>, |
417 | pub(crate) results: &'x AuthenticationResults<'x>, |
418 | @@ -117,8 +117,8 @@ impl<'x> VerifySignature for Seal<'x> { |
419 | } |
420 | } |
421 | |
422 | - impl<'x> ARCOutput<'x> { |
423 | - pub(crate) fn with_result(mut self, result: DKIMResult) -> Self { |
424 | + impl<'x> ArcOutput<'x> { |
425 | + pub(crate) fn with_result(mut self, result: DkimResult) -> Self { |
426 | self.result = result; |
427 | self |
428 | } |
429 | @@ -128,10 +128,10 @@ impl<'x> ARCOutput<'x> { |
430 | } |
431 | } |
432 | |
433 | - impl<'x> Default for ARCOutput<'x> { |
434 | + impl<'x> Default for ArcOutput<'x> { |
435 | fn default() -> Self { |
436 | Self { |
437 | - result: DKIMResult::None, |
438 | + result: DkimResult::None, |
439 | set: Vec::new(), |
440 | } |
441 | } |
442 | diff --git a/src/arc/seal.rs b/src/arc/seal.rs |
443 | index d60aa39..f963ca5 100644 |
444 | --- a/src/arc/seal.rs |
445 | +++ b/src/arc/seal.rs |
446 | @@ -18,14 +18,14 @@ use sha2::Sha256; |
447 | |
448 | use crate::{ |
449 | dkim::{Algorithm, Canonicalization}, |
450 | - ARCOutput, AuthenticatedMessage, AuthenticationResults, DKIMResult, Error, PrivateKey, |
451 | + ArcOutput, AuthenticatedMessage, AuthenticationResults, DkimResult, Error, PrivateKey, |
452 | }; |
453 | |
454 | - use super::{ChainValidation, Seal, Signature, ARC}; |
455 | + use super::{ArcSet, ChainValidation, Seal, Signature}; |
456 | |
457 | - impl<'x> ARC<'x> { |
458 | + impl<'x> ArcSet<'x> { |
459 | pub fn new(results: &'x AuthenticationResults) -> Self { |
460 | - ARC { |
461 | + ArcSet { |
462 | signature: Signature::default(), |
463 | seal: Seal::default(), |
464 | results, |
465 | @@ -35,7 +35,7 @@ impl<'x> ARC<'x> { |
466 | pub fn seal( |
467 | mut self, |
468 | message: &'x AuthenticatedMessage<'x>, |
469 | - arc_output: &ARCOutput, |
470 | + arc_output: &ArcOutput, |
471 | with_key: &PrivateKey, |
472 | ) -> crate::Result<Self> { |
473 | if !arc_output.can_be_sealed() { |
474 | @@ -61,7 +61,7 @@ impl<'x> ARC<'x> { |
475 | self.signature.i = i; |
476 | self.seal.i = i; |
477 | self.seal.cv = match &arc_output.result { |
478 | - DKIMResult::Pass => ChainValidation::Pass, |
479 | + DkimResult::Pass => ChainValidation::Pass, |
480 | _ => ChainValidation::Fail, |
481 | }; |
482 | } |
483 | @@ -246,10 +246,10 @@ mod test { |
484 | use mail_parser::decoders::base64::base64_decode; |
485 | |
486 | use crate::{ |
487 | - arc::ARC, |
488 | + arc::ArcSet, |
489 | common::{headers::HeaderWriter, parse::TxtRecordParser}, |
490 | dkim::{DomainKey, Signature}, |
491 | - AuthenticatedMessage, AuthenticationResults, DKIMResult, PrivateKey, Resolver, |
492 | + AuthenticatedMessage, AuthenticationResults, DkimResult, PrivateKey, Resolver, |
493 | }; |
494 | |
495 | const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY----- |
496 | @@ -344,12 +344,12 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
497 | let dkim_result = resolver.verify_dkim(&message).await; |
498 | let arc_result = resolver.verify_arc(&message).await; |
499 | assert!( |
500 | - matches!(arc_result.result(), DKIMResult::Pass | DKIMResult::None), |
501 | + matches!(arc_result.result(), DkimResult::Pass | DkimResult::None), |
502 | "ARC validation failed: {:?}", |
503 | arc_result.result() |
504 | ); |
505 | let auth_results = AuthenticationResults::new(d).with_dkim_result(&dkim_result, d); |
506 | - let arc = ARC::new(&auth_results) |
507 | + let arc = ArcSet::new(&auth_results) |
508 | .domain(d) |
509 | .selector(s) |
510 | .headers(["From", "To", "Subject", "DKIM-Signature"]) |
511 | diff --git a/src/arc/verify.rs b/src/arc/verify.rs |
512 | index db793d3..e78abd1 100644 |
513 | --- a/src/arc/verify.rs |
514 | +++ b/src/arc/verify.rs |
515 | @@ -16,22 +16,23 @@ use sha2::Sha256; |
516 | use crate::{ |
517 | common::{headers::Header, verify::VerifySignature}, |
518 | dkim::{verify::Verifier, Algorithm, Canonicalization, DomainKey, HashAlgorithm}, |
519 | - ARCOutput, AuthenticatedMessage, DKIMResult, Error, Resolver, |
520 | + ArcOutput, AuthenticatedMessage, DkimResult, Error, Resolver, |
521 | }; |
522 | |
523 | use super::{ChainValidation, Set}; |
524 | |
525 | impl Resolver { |
526 | - pub async fn verify_arc<'x>(&self, message: &'x AuthenticatedMessage<'x>) -> ARCOutput<'x> { |
527 | + /// Verifies ARC headers of an RFC5322 message. |
528 | + pub async fn verify_arc<'x>(&self, message: &'x AuthenticatedMessage<'x>) -> ArcOutput<'x> { |
529 | let arc_headers = message.ams_headers.len(); |
530 | if arc_headers == 0 { |
531 | - return ARCOutput::default(); |
532 | + return ArcOutput::default(); |
533 | } else if arc_headers > 50 { |
534 | - return ARCOutput::default().with_result(DKIMResult::Fail(Error::ARCChainTooLong)); |
535 | + return ArcOutput::default().with_result(DkimResult::Fail(Error::ARCChainTooLong)); |
536 | } else if (arc_headers != message.as_headers.len()) |
537 | || (arc_headers != message.aar_headers.len()) |
538 | { |
539 | - return ARCOutput::default().with_result(DKIMResult::Fail(Error::ARCBrokenChain)); |
540 | + return ArcOutput::default().with_result(DkimResult::Fail(Error::ARCBrokenChain)); |
541 | } |
542 | |
543 | let now = SystemTime::now() |
544 | @@ -39,8 +40,8 @@ impl Resolver { |
545 | .map(|d| d.as_secs()) |
546 | .unwrap_or(0); |
547 | |
548 | - let mut output = ARCOutput { |
549 | - result: DKIMResult::None, |
550 | + let mut output = ArcOutput { |
551 | + result: DkimResult::None, |
552 | set: Vec::with_capacity(message.aar_headers.len() / 3), |
553 | }; |
554 | |
555 | @@ -54,27 +55,27 @@ impl Resolver { |
556 | { |
557 | let seal = match &seal_.header { |
558 | Ok(seal) => seal, |
559 | - Err(err) => return output.with_result(DKIMResult::Neutral(err.clone())), |
560 | + Err(err) => return output.with_result(DkimResult::Neutral(err.clone())), |
561 | }; |
562 | let signature = match &signature_.header { |
563 | Ok(signature) => signature, |
564 | - Err(err) => return output.with_result(DKIMResult::Neutral(err.clone())), |
565 | + Err(err) => return output.with_result(DkimResult::Neutral(err.clone())), |
566 | }; |
567 | let results = match &results_.header { |
568 | Ok(results) => results, |
569 | - Err(err) => return output.with_result(DKIMResult::Neutral(err.clone())), |
570 | + Err(err) => return output.with_result(DkimResult::Neutral(err.clone())), |
571 | }; |
572 | |
573 | - if output.result == DKIMResult::None { |
574 | + if output.result == DkimResult::None { |
575 | if (seal.i as usize != (pos + 1)) |
576 | || (signature.i as usize != (pos + 1)) |
577 | || (results.i as usize != (pos + 1)) |
578 | { |
579 | - output.result = DKIMResult::Fail(Error::ARCInvalidInstance((pos + 1) as u32)); |
580 | + output.result = DkimResult::Fail(Error::ARCInvalidInstance((pos + 1) as u32)); |
581 | } else if (pos == 0 && seal.cv != ChainValidation::None) |
582 | || (pos > 0 && seal.cv != ChainValidation::Pass) |
583 | { |
584 | - output.result = DKIMResult::Fail(Error::ARCInvalidCV); |
585 | + output.result = DkimResult::Fail(Error::ARCInvalidCV); |
586 | } else if pos == arc_headers - 1 { |
587 | // Validate last signature in the chain |
588 | if signature.x == 0 || (signature.x > signature.t && signature.x > now) { |
589 | @@ -89,10 +90,10 @@ impl Resolver { |
590 | .unwrap() |
591 | .3; |
592 | if bh != &signature.bh { |
593 | - output.result = DKIMResult::Neutral(Error::FailedBodyHashMatch); |
594 | + output.result = DkimResult::Neutral(Error::FailedBodyHashMatch); |
595 | } |
596 | } else { |
597 | - output.result = DKIMResult::Neutral(Error::SignatureExpired); |
598 | + output.result = DkimResult::Neutral(Error::SignatureExpired); |
599 | } |
600 | } |
601 | } |
602 | @@ -104,7 +105,7 @@ impl Resolver { |
603 | }); |
604 | } |
605 | |
606 | - if output.result != DKIMResult::None { |
607 | + if output.result != DkimResult::None { |
608 | return output; |
609 | } |
610 | |
611 | @@ -134,7 +135,7 @@ impl Resolver { |
612 | |
613 | // Verify signature |
614 | if let Err(err) = signature.verify(record.as_ref(), &hh) { |
615 | - return output.with_result(DKIMResult::Fail(err)); |
616 | + return output.with_result(DkimResult::Fail(err)); |
617 | } |
618 | |
619 | // Validate ARC Seals |
620 | @@ -178,12 +179,12 @@ impl Resolver { |
621 | |
622 | // Verify ARC Seal |
623 | if let Err(err) = seal.verify(record.as_ref(), &hh) { |
624 | - return output.with_result(DKIMResult::Fail(err)); |
625 | + return output.with_result(DkimResult::Fail(err)); |
626 | } |
627 | } |
628 | |
629 | // ARC Validation successful |
630 | - output.with_result(DKIMResult::Pass) |
631 | + output.with_result(DkimResult::Pass) |
632 | } |
633 | } |
634 | |
635 | @@ -196,7 +197,7 @@ mod test { |
636 | }; |
637 | |
638 | use crate::{ |
639 | - common::parse::TxtRecordParser, dkim::DomainKey, AuthenticatedMessage, DKIMResult, Resolver, |
640 | + common::parse::TxtRecordParser, dkim::DomainKey, AuthenticatedMessage, DkimResult, Resolver, |
641 | }; |
642 | |
643 | #[tokio::test] |
644 | @@ -219,10 +220,10 @@ mod test { |
645 | let message = AuthenticatedMessage::parse(raw_message.as_bytes()).unwrap(); |
646 | |
647 | let arc = resolver.verify_arc(&message).await; |
648 | - assert_eq!(arc.result(), &DKIMResult::Pass); |
649 | + assert_eq!(arc.result(), &DkimResult::Pass); |
650 | |
651 | let dkim = resolver.verify_dkim(&message).await; |
652 | - assert!(dkim.iter().any(|o| o.result() == &DKIMResult::Pass)); |
653 | + assert!(dkim.iter().any(|o| o.result() == &DkimResult::Pass)); |
654 | } |
655 | } |
656 | |
657 | diff --git a/src/common/auth_results.rs b/src/common/auth_results.rs |
658 | index 301b728..3e727ca 100644 |
659 | --- a/src/common/auth_results.rs |
660 | +++ b/src/common/auth_results.rs |
661 | @@ -8,13 +8,13 @@ |
662 | * except according to those terms. |
663 | */ |
664 | |
665 | - use std::{borrow::Cow, fmt::Write, net::IpAddr}; |
666 | + use std::{borrow::Cow, fmt::Write, io, net::IpAddr}; |
667 | |
668 | use mail_builder::encoders::base64::base64_encode; |
669 | |
670 | use crate::{ |
671 | - ARCOutput, AuthenticationResults, DKIMOutput, DKIMResult, DMARCOutput, DMARCResult, Error, |
672 | - ReceivedSPF, SPFOutput, SPFResult, |
673 | + ArcOutput, AuthenticationResults, DkimOutput, DkimResult, DmarcOutput, DmarcResult, Error, |
674 | + ReceivedSpf, SpfOutput, SpfResult, |
675 | }; |
676 | |
677 | use super::headers::HeaderWriter; |
678 | @@ -27,7 +27,7 @@ impl<'x> AuthenticationResults<'x> { |
679 | } |
680 | } |
681 | |
682 | - pub fn with_dkim_result(mut self, dkim: &[DKIMOutput], header_from: &str) -> Self { |
683 | + pub fn with_dkim_result(mut self, dkim: &[DkimOutput], header_from: &str) -> Self { |
684 | for dkim in dkim { |
685 | if !dkim.is_atps { |
686 | self.auth_results.push_str(";\r\n\tdkim="); |
687 | @@ -63,7 +63,7 @@ impl<'x> AuthenticationResults<'x> { |
688 | |
689 | pub fn with_spf_result( |
690 | mut self, |
691 | - spf: &SPFOutput, |
692 | + spf: &SpfOutput, |
693 | ip_addr: IpAddr, |
694 | helo: &str, |
695 | mail_from: &str, |
696 | @@ -89,23 +89,23 @@ impl<'x> AuthenticationResults<'x> { |
697 | self |
698 | } |
699 | |
700 | - pub fn with_arc_result(mut self, arc: &ARCOutput, remote_ip: IpAddr) -> Self { |
701 | + pub fn with_arc_result(mut self, arc: &ArcOutput, remote_ip: IpAddr) -> Self { |
702 | self.auth_results.push_str(";\r\n\tarc="); |
703 | arc.result.as_auth_result(&mut self.auth_results); |
704 | write!(self.auth_results, " smtp.remote-ip={}", remote_ip).ok(); |
705 | self |
706 | } |
707 | |
708 | - pub fn with_dmarc_result(mut self, dmarc: &DMARCOutput) -> Self { |
709 | + pub fn with_dmarc_result(mut self, dmarc: &DmarcOutput) -> Self { |
710 | self.auth_results.push_str(";\r\n\tdmarc="); |
711 | - if dmarc.spf_result == DMARCResult::Pass || dmarc.dkim_result == DMARCResult::Pass { |
712 | - DMARCResult::Pass.as_auth_result(&mut self.auth_results); |
713 | - } else if dmarc.spf_result != DMARCResult::None { |
714 | + if dmarc.spf_result == DmarcResult::Pass || dmarc.dkim_result == DmarcResult::Pass { |
715 | + DmarcResult::Pass.as_auth_result(&mut self.auth_results); |
716 | + } else if dmarc.spf_result != DmarcResult::None { |
717 | dmarc.spf_result.as_auth_result(&mut self.auth_results); |
718 | - } else if dmarc.dkim_result != DMARCResult::None { |
719 | + } else if dmarc.dkim_result != DmarcResult::None { |
720 | dmarc.dkim_result.as_auth_result(&mut self.auth_results); |
721 | } else { |
722 | - DMARCResult::None.as_auth_result(&mut self.auth_results); |
723 | + DmarcResult::None.as_auth_result(&mut self.auth_results); |
724 | } |
725 | write!( |
726 | self.auth_results, |
727 | @@ -118,7 +118,7 @@ impl<'x> AuthenticationResults<'x> { |
728 | } |
729 | |
730 | impl<'x> HeaderWriter for AuthenticationResults<'x> { |
731 | - fn write_header(&self, mut writer: impl std::io::Write) -> std::io::Result<()> { |
732 | + fn write_header(&self, mut writer: impl io::Write) -> io::Result<()> { |
733 | writer.write_all(b"Authentication-Results: ")?; |
734 | writer.write_all(self.hostname.as_bytes())?; |
735 | if !self.auth_results.is_empty() { |
736 | @@ -130,17 +130,17 @@ impl<'x> HeaderWriter for AuthenticationResults<'x> { |
737 | } |
738 | } |
739 | |
740 | - impl HeaderWriter for ReceivedSPF { |
741 | - fn write_header(&self, mut writer: impl std::io::Write) -> std::io::Result<()> { |
742 | + impl HeaderWriter for ReceivedSpf { |
743 | + fn write_header(&self, mut writer: impl io::Write) -> io::Result<()> { |
744 | writer.write_all(b"Received-SPF: ")?; |
745 | writer.write_all(self.received_spf.as_bytes())?; |
746 | writer.write_all(b"\r\n") |
747 | } |
748 | } |
749 | |
750 | - impl ReceivedSPF { |
751 | + impl ReceivedSpf { |
752 | pub fn new( |
753 | - spf: &SPFOutput, |
754 | + spf: &SpfOutput, |
755 | ip_addr: IpAddr, |
756 | helo: &str, |
757 | mail_from: &str, |
758 | @@ -163,44 +163,44 @@ impl ReceivedSPF { |
759 | ) |
760 | .ok(); |
761 | |
762 | - ReceivedSPF { received_spf } |
763 | + ReceivedSpf { received_spf } |
764 | } |
765 | } |
766 | |
767 | - impl SPFResult { |
768 | + impl SpfResult { |
769 | fn as_spf_result(&self, header: &mut String, hostname: &str, mail_from: &str, ip_addr: IpAddr) { |
770 | match &self { |
771 | - SPFResult::Pass => write!( |
772 | + SpfResult::Pass => write!( |
773 | header, |
774 | "pass ({}: domain of {} designates {} as permitted sender)", |
775 | hostname, mail_from, ip_addr |
776 | ), |
777 | - SPFResult::Fail => write!( |
778 | + SpfResult::Fail => write!( |
779 | header, |
780 | "fail ({}: domain of {} does not designate {} as permitted sender)", |
781 | hostname, mail_from, ip_addr |
782 | ), |
783 | - SPFResult::SoftFail => write!( |
784 | + SpfResult::SoftFail => write!( |
785 | header, |
786 | "softfail ({}: domain of {} reports soft fail for {})", |
787 | hostname, mail_from, ip_addr |
788 | ), |
789 | - SPFResult::Neutral => write!( |
790 | + SpfResult::Neutral => write!( |
791 | header, |
792 | "neutral ({}: domain of {} reports neutral for {})", |
793 | hostname, mail_from, ip_addr |
794 | ), |
795 | - SPFResult::TempError => write!( |
796 | + SpfResult::TempError => write!( |
797 | header, |
798 | "temperror ({}: temporary dns error validating {})", |
799 | hostname, mail_from |
800 | ), |
801 | - SPFResult::PermError => write!( |
802 | + SpfResult::PermError => write!( |
803 | header, |
804 | "permerror ({}: unable to verify SPF record for {})", |
805 | hostname, mail_from, |
806 | ), |
807 | - SPFResult::None => write!( |
808 | + SpfResult::None => write!( |
809 | header, |
810 | "none ({}: no SPF records found for {})", |
811 | hostname, mail_from |
812 | @@ -214,48 +214,48 @@ pub trait AsAuthResult { |
813 | fn as_auth_result(&self, header: &mut String); |
814 | } |
815 | |
816 | - impl AsAuthResult for DMARCResult { |
817 | + impl AsAuthResult for DmarcResult { |
818 | fn as_auth_result(&self, header: &mut String) { |
819 | match &self { |
820 | - DMARCResult::Pass => header.push_str("pass"), |
821 | - DMARCResult::Fail(err) => { |
822 | + DmarcResult::Pass => header.push_str("pass"), |
823 | + DmarcResult::Fail(err) => { |
824 | header.push_str("fail"); |
825 | err.as_auth_result(header); |
826 | } |
827 | - DMARCResult::PermError(err) => { |
828 | + DmarcResult::PermError(err) => { |
829 | header.push_str("permerror"); |
830 | err.as_auth_result(header); |
831 | } |
832 | - DMARCResult::TempError(err) => { |
833 | + DmarcResult::TempError(err) => { |
834 | header.push_str("temperror"); |
835 | err.as_auth_result(header); |
836 | } |
837 | - DMARCResult::None => header.push_str("none"), |
838 | + DmarcResult::None => header.push_str("none"), |
839 | } |
840 | } |
841 | } |
842 | |
843 | - impl AsAuthResult for DKIMResult { |
844 | + impl AsAuthResult for DkimResult { |
845 | fn as_auth_result(&self, header: &mut String) { |
846 | match &self { |
847 | - DKIMResult::Pass => header.push_str("pass"), |
848 | - DKIMResult::Neutral(err) => { |
849 | + DkimResult::Pass => header.push_str("pass"), |
850 | + DkimResult::Neutral(err) => { |
851 | header.push_str("neutral"); |
852 | err.as_auth_result(header); |
853 | } |
854 | - DKIMResult::Fail(err) => { |
855 | + DkimResult::Fail(err) => { |
856 | header.push_str("fail"); |
857 | err.as_auth_result(header); |
858 | } |
859 | - DKIMResult::PermError(err) => { |
860 | + DkimResult::PermError(err) => { |
861 | header.push_str("permerror"); |
862 | err.as_auth_result(header); |
863 | } |
864 | - DKIMResult::TempError(err) => { |
865 | + DkimResult::TempError(err) => { |
866 | header.push_str("temperror"); |
867 | err.as_auth_result(header); |
868 | } |
869 | - DKIMResult::None => header.push_str("none"), |
870 | + DkimResult::None => header.push_str("none"), |
871 | } |
872 | } |
873 | } |
874 | @@ -300,8 +300,8 @@ impl AsAuthResult for Error { |
875 | #[cfg(test)] |
876 | mod test { |
877 | use crate::{ |
878 | - dkim::Signature, dmarc::Policy, ARCOutput, AuthenticationResults, DKIMOutput, DKIMResult, |
879 | - DMARCOutput, DMARCResult, Error, ReceivedSPF, SPFOutput, SPFResult, |
880 | + dkim::Signature, dmarc::Policy, ArcOutput, AuthenticationResults, DkimOutput, DkimResult, |
881 | + DmarcOutput, DmarcResult, Error, ReceivedSpf, SpfOutput, SpfResult, |
882 | }; |
883 | |
884 | #[test] |
885 | @@ -311,8 +311,8 @@ mod test { |
886 | for (expected_auth_results, dkim) in [ |
887 | ( |
888 | "dkim=pass header.d=example.org header.s=myselector", |
889 | - DKIMOutput { |
890 | - result: DKIMResult::Pass, |
891 | + DkimOutput { |
892 | + result: DkimResult::Pass, |
893 | signature: (&Signature { |
894 | d: "example.org".into(), |
895 | s: "myselector".into(), |
896 | @@ -328,8 +328,8 @@ mod test { |
897 | "dkim=fail (verification failed) header.d=example.org ", |
898 | "header.s=myselector header.b=MTIzNDU2" |
899 | ), |
900 | - DKIMOutput { |
901 | - result: DKIMResult::Fail(Error::FailedVerification), |
902 | + DkimOutput { |
903 | + result: DkimResult::Fail(Error::FailedVerification), |
904 | signature: (&Signature { |
905 | d: "example.org".into(), |
906 | s: "myselector".into(), |
907 | @@ -346,8 +346,8 @@ mod test { |
908 | "dkim-atps=temperror (dns error) header.d=atps.example.org ", |
909 | "header.s=otherselctor header.b=YWJjZGVm header.from=jdoe@example.org" |
910 | ), |
911 | - DKIMOutput { |
912 | - result: DKIMResult::TempError(Error::DNSError), |
913 | + DkimOutput { |
914 | + result: DkimResult::TempError(Error::DNSError), |
915 | signature: (&Signature { |
916 | d: "atps.example.org".into(), |
917 | s: "otherselctor".into(), |
918 | @@ -386,7 +386,7 @@ mod test { |
919 | "permitted sender)\r\n\treceiver=localhost; client-ip=192.168.1.1; ", |
920 | "envelope-from=\"jdoe@example.org\"; helo=example.org;" |
921 | ), |
922 | - SPFResult::Pass, |
923 | + SpfResult::Pass, |
924 | "192.168.1.1".parse().unwrap(), |
925 | "localhost", |
926 | "example.org", |
927 | @@ -404,7 +404,7 @@ mod test { |
928 | "client-ip=a:b:c::f; envelope-from=\"sender@otherdomain.org\"; ", |
929 | "helo=otherdomain.org;" |
930 | ), |
931 | - SPFResult::Fail, |
932 | + SpfResult::Fail, |
933 | "a:b:c::f".parse().unwrap(), |
934 | "mx.domain.org", |
935 | "otherdomain.org", |
936 | @@ -420,7 +420,7 @@ mod test { |
937 | "a:b:c::f)\r\n\treceiver=mx.domain.org; client-ip=a:b:c::f; ", |
938 | "envelope-from=\"postmaster@example.org\"; helo=example.org;" |
939 | ), |
940 | - SPFResult::Neutral, |
941 | + SpfResult::Neutral, |
942 | "a:b:c::f".parse().unwrap(), |
943 | "mx.domain.org", |
944 | "example.org", |
945 | @@ -429,7 +429,7 @@ mod test { |
946 | ] { |
947 | auth_results.hostname = receiver; |
948 | auth_results = auth_results.with_spf_result( |
949 | - &SPFOutput { |
950 | + &SpfOutput { |
951 | result, |
952 | domain: "".to_string(), |
953 | report: None, |
954 | @@ -439,8 +439,8 @@ mod test { |
955 | helo, |
956 | mail_from, |
957 | ); |
958 | - let received_spf = ReceivedSPF::new( |
959 | - &SPFOutput { |
960 | + let received_spf = ReceivedSpf::new( |
961 | + &SpfOutput { |
962 | result, |
963 | domain: "".to_string(), |
964 | report: None, |
965 | @@ -461,9 +461,9 @@ mod test { |
966 | for (expected_auth_results, dmarc) in [ |
967 | ( |
968 | "dmarc=pass header.from=example.org policy.dmarc=none", |
969 | - DMARCOutput { |
970 | - spf_result: DMARCResult::Pass, |
971 | - dkim_result: DMARCResult::None, |
972 | + DmarcOutput { |
973 | + spf_result: DmarcResult::Pass, |
974 | + dkim_result: DmarcResult::None, |
975 | domain: "example.org".to_string(), |
976 | policy: Policy::None, |
977 | record: None, |
978 | @@ -471,9 +471,9 @@ mod test { |
979 | ), |
980 | ( |
981 | "dmarc=fail (dmarc not aligned) header.from=example.com policy.dmarc=quarantine", |
982 | - DMARCOutput { |
983 | - dkim_result: DMARCResult::Fail(Error::DMARCNotAligned), |
984 | - spf_result: DMARCResult::None, |
985 | + DmarcOutput { |
986 | + dkim_result: DmarcResult::Fail(Error::DMARCNotAligned), |
987 | + spf_result: DmarcResult::None, |
988 | domain: "example.com".to_string(), |
989 | policy: Policy::Quarantine, |
990 | record: None, |
991 | @@ -490,17 +490,17 @@ mod test { |
992 | for (expected_auth_results, arc, remote_ip) in [ |
993 | ( |
994 | "arc=pass smtp.remote-ip=192.127.9.2", |
995 | - DKIMResult::Pass, |
996 | + DkimResult::Pass, |
997 | "192.127.9.2".parse().unwrap(), |
998 | ), |
999 | ( |
1000 | "arc=neutral (body hash did not verify) smtp.remote-ip=1:2:3::a", |
1001 | - DKIMResult::Neutral(Error::FailedBodyHashMatch), |
1002 | + DkimResult::Neutral(Error::FailedBodyHashMatch), |
1003 | "1:2:3::a".parse().unwrap(), |
1004 | ), |
1005 | ] { |
1006 | auth_results = auth_results.with_arc_result( |
1007 | - &ARCOutput { |
1008 | + &ArcOutput { |
1009 | result: arc, |
1010 | set: vec![], |
1011 | }, |
1012 | diff --git a/src/common/base32.rs b/src/common/base32.rs |
1013 | index 0e8fbca..d2ecf63 100644 |
1014 | --- a/src/common/base32.rs |
1015 | +++ b/src/common/base32.rs |
1016 | @@ -8,6 +8,8 @@ |
1017 | * except according to those terms. |
1018 | */ |
1019 | |
1020 | + use std::io; |
1021 | + |
1022 | pub(crate) static BASE32_ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; |
1023 | |
1024 | pub(crate) struct Base32Writer { |
1025 | @@ -66,8 +68,8 @@ impl Base32Writer { |
1026 | } |
1027 | } |
1028 | |
1029 | - impl std::io::Write for Base32Writer { |
1030 | - fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> { |
1031 | + impl io::Write for Base32Writer { |
1032 | + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { |
1033 | let start_pos = self.pos; |
1034 | |
1035 | for &byte in bytes { |
1036 | @@ -77,13 +79,14 @@ impl std::io::Write for Base32Writer { |
1037 | Ok(self.pos - start_pos) |
1038 | } |
1039 | |
1040 | - fn flush(&mut self) -> std::io::Result<()> { |
1041 | + fn flush(&mut self) -> io::Result<()> { |
1042 | Ok(()) |
1043 | } |
1044 | } |
1045 | |
1046 | #[cfg(test)] |
1047 | mod tests { |
1048 | + |
1049 | use std::io::Write; |
1050 | |
1051 | use sha1::{Digest, Sha1}; |
1052 | diff --git a/src/common/headers.rs b/src/common/headers.rs |
1053 | index ff45eaa..d2673eb 100644 |
1054 | --- a/src/common/headers.rs |
1055 | +++ b/src/common/headers.rs |
1056 | @@ -9,7 +9,7 @@ |
1057 | */ |
1058 | |
1059 | use std::{ |
1060 | - io::Write, |
1061 | + io, |
1062 | iter::{Enumerate, Peekable}, |
1063 | slice::Iter, |
1064 | }; |
1065 | @@ -266,7 +266,7 @@ impl<'x> Iterator for HeaderParser<'x> { |
1066 | } |
1067 | |
1068 | pub trait HeaderWriter: Sized { |
1069 | - fn write_header(&self, writer: impl Write) -> std::io::Result<()>; |
1070 | + fn write_header(&self, writer: impl io::Write) -> io::Result<()>; |
1071 | fn to_header(&self) -> String { |
1072 | let mut buf = Vec::new(); |
1073 | self.write_header(&mut buf).unwrap(); |
1074 | diff --git a/src/common/resolver.rs b/src/common/resolver.rs |
1075 | index f4e05e1..9a8b9ca 100644 |
1076 | --- a/src/common/resolver.rs |
1077 | +++ b/src/common/resolver.rs |
1078 | @@ -24,8 +24,8 @@ use trust_dns_resolver::{ |
1079 | |
1080 | use crate::{ |
1081 | dkim::{Atps, DomainKey, DomainKeyReport}, |
1082 | - dmarc::DMARC, |
1083 | - spf::{Macro, SPF}, |
1084 | + dmarc::Dmarc, |
1085 | + spf::{Macro, Spf}, |
1086 | Error, Policy, Resolver, Txt, MX, |
1087 | }; |
1088 | |
1089 | @@ -356,21 +356,21 @@ impl From<Atps> for Txt { |
1090 | } |
1091 | } |
1092 | |
1093 | - impl From<SPF> for Txt { |
1094 | - fn from(v: SPF) -> Self { |
1095 | - Txt::SPF(v.into()) |
1096 | + impl From<Spf> for Txt { |
1097 | + fn from(v: Spf) -> Self { |
1098 | + Txt::Spf(v.into()) |
1099 | } |
1100 | } |
1101 | |
1102 | impl From<Macro> for Txt { |
1103 | fn from(v: Macro) -> Self { |
1104 | - Txt::SPFMacro(v.into()) |
1105 | + Txt::SpfMacro(v.into()) |
1106 | } |
1107 | } |
1108 | |
1109 | - impl From<DMARC> for Txt { |
1110 | - fn from(v: DMARC) -> Self { |
1111 | - Txt::DMARC(v.into()) |
1112 | + impl From<Dmarc> for Txt { |
1113 | + fn from(v: Dmarc) -> Self { |
1114 | + Txt::Dmarc(v.into()) |
1115 | } |
1116 | } |
1117 | |
1118 | @@ -417,10 +417,10 @@ impl UnwrapTxtRecord for Atps { |
1119 | } |
1120 | } |
1121 | |
1122 | - impl UnwrapTxtRecord for SPF { |
1123 | + impl UnwrapTxtRecord for Spf { |
1124 | fn unwrap_txt(txt: Txt) -> crate::Result<Arc<Self>> { |
1125 | match txt { |
1126 | - Txt::SPF(a) => Ok(a), |
1127 | + Txt::Spf(a) => Ok(a), |
1128 | Txt::Error(err) => Err(err), |
1129 | _ => Err(Error::Io("Invalid record type".to_string())), |
1130 | } |
1131 | @@ -430,17 +430,17 @@ impl UnwrapTxtRecord for SPF { |
1132 | impl UnwrapTxtRecord for Macro { |
1133 | fn unwrap_txt(txt: Txt) -> crate::Result<Arc<Self>> { |
1134 | match txt { |
1135 | - Txt::SPFMacro(a) => Ok(a), |
1136 | + Txt::SpfMacro(a) => Ok(a), |
1137 | Txt::Error(err) => Err(err), |
1138 | _ => Err(Error::Io("Invalid record type".to_string())), |
1139 | } |
1140 | } |
1141 | } |
1142 | |
1143 | - impl UnwrapTxtRecord for DMARC { |
1144 | + impl UnwrapTxtRecord for Dmarc { |
1145 | fn unwrap_txt(txt: Txt) -> crate::Result<Arc<Self>> { |
1146 | match txt { |
1147 | - Txt::DMARC(a) => Ok(a), |
1148 | + Txt::Dmarc(a) => Ok(a), |
1149 | Txt::Error(err) => Err(err), |
1150 | _ => Err(Error::Io("Invalid record type".to_string())), |
1151 | } |
1152 | diff --git a/src/dkim/canonicalize.rs b/src/dkim/canonicalize.rs |
1153 | index ef34159..7ed0383 100644 |
1154 | --- a/src/dkim/canonicalize.rs |
1155 | +++ b/src/dkim/canonicalize.rs |
1156 | @@ -8,7 +8,10 @@ |
1157 | * except according to those terms. |
1158 | */ |
1159 | |
1160 | - use std::{borrow::Cow, io::Write}; |
1161 | + use std::{ |
1162 | + borrow::Cow, |
1163 | + io::{self}, |
1164 | + }; |
1165 | |
1166 | use sha1::Digest; |
1167 | |
1168 | @@ -17,7 +20,7 @@ use crate::common::headers::HeaderIterator; |
1169 | use super::{Canonicalization, Signature}; |
1170 | |
1171 | impl Canonicalization { |
1172 | - pub fn canonicalize_body(&self, body: &[u8], mut hasher: impl Write) -> std::io::Result<()> { |
1173 | + pub fn canonicalize_body(&self, body: &[u8], mut hasher: impl io::Write) -> io::Result<()> { |
1174 | let mut crlf_seq = 0; |
1175 | |
1176 | match self { |
1177 | @@ -78,8 +81,8 @@ impl Canonicalization { |
1178 | pub fn canonicalize_headers<'x>( |
1179 | &self, |
1180 | headers: impl Iterator<Item = (&'x [u8], &'x [u8])>, |
1181 | - mut hasher: impl Write, |
1182 | - ) -> std::io::Result<()> { |
1183 | + mut hasher: impl io::Write, |
1184 | + ) -> io::Result<()> { |
1185 | match self { |
1186 | Canonicalization::Relaxed => { |
1187 | for (name, value) in headers { |
1188 | @@ -121,18 +124,18 @@ impl Canonicalization { |
1189 | pub fn hash_headers<'x, T>( |
1190 | &self, |
1191 | headers: impl Iterator<Item = (&'x [u8], &'x [u8])>, |
1192 | - ) -> std::io::Result<Vec<u8>> |
1193 | + ) -> io::Result<Vec<u8>> |
1194 | where |
1195 | - T: Digest + std::io::Write, |
1196 | + T: Digest + io::Write, |
1197 | { |
1198 | let mut hasher = T::new(); |
1199 | self.canonicalize_headers(headers, &mut hasher)?; |
1200 | Ok(hasher.finalize().to_vec()) |
1201 | } |
1202 | |
1203 | - pub fn hash_body<T>(&self, body: &[u8], l: u64) -> std::io::Result<Vec<u8>> |
1204 | + pub fn hash_body<T>(&self, body: &[u8], l: u64) -> io::Result<Vec<u8>> |
1205 | where |
1206 | - T: Digest + std::io::Write, |
1207 | + T: Digest + io::Write, |
1208 | { |
1209 | let mut hasher = T::new(); |
1210 | self.canonicalize_body( |
1211 | @@ -146,7 +149,7 @@ impl Canonicalization { |
1212 | Ok(hasher.finalize().to_vec()) |
1213 | } |
1214 | |
1215 | - pub fn serialize_name(&self, mut writer: impl Write) -> std::io::Result<()> { |
1216 | + pub fn serialize_name(&self, mut writer: impl io::Write) -> io::Result<()> { |
1217 | writer.write_all(match self { |
1218 | Canonicalization::Relaxed => b"relaxed", |
1219 | Canonicalization::Simple => b"simple", |
1220 | @@ -159,8 +162,8 @@ impl<'x> Signature<'x> { |
1221 | pub(crate) fn canonicalize( |
1222 | &self, |
1223 | message: &'x [u8], |
1224 | - header_hasher: impl Write, |
1225 | - body_hasher: impl Write, |
1226 | + header_hasher: impl io::Write, |
1227 | + body_hasher: impl io::Write, |
1228 | ) -> crate::Result<(usize, Vec<Cow<'x, str>>)> { |
1229 | let mut headers_it = HeaderIterator::new(message); |
1230 | let mut headers = Vec::with_capacity(self.h.len()); |
1231 | diff --git a/src/dkim/headers.rs b/src/dkim/headers.rs |
1232 | index 08b1b0b..880d302 100644 |
1233 | --- a/src/dkim/headers.rs |
1234 | +++ b/src/dkim/headers.rs |
1235 | @@ -10,7 +10,7 @@ |
1236 | |
1237 | use std::{ |
1238 | fmt::{Display, Formatter}, |
1239 | - io::Write, |
1240 | + io, |
1241 | }; |
1242 | |
1243 | use crate::common::headers::HeaderWriter; |
1244 | @@ -18,7 +18,7 @@ use crate::common::headers::HeaderWriter; |
1245 | use super::{Algorithm, Canonicalization, HashAlgorithm, Signature}; |
1246 | |
1247 | impl<'x> Signature<'x> { |
1248 | - pub(crate) fn write(&self, mut writer: impl Write, as_header: bool) -> std::io::Result<()> { |
1249 | + pub(crate) fn write(&self, mut writer: impl io::Write, as_header: bool) -> io::Result<()> { |
1250 | let (header, new_line) = match self.ch { |
1251 | Canonicalization::Relaxed if !as_header => (&b"dkim-signature:"[..], &b" "[..]), |
1252 | _ => (&b"DKIM-Signature: "[..], &b"\r\n\t"[..]), |
1253 | @@ -136,7 +136,7 @@ impl<'x> Signature<'x> { |
1254 | } |
1255 | |
1256 | impl<'x> HeaderWriter for Signature<'x> { |
1257 | - fn write_header(&self, writer: impl Write) -> std::io::Result<()> { |
1258 | + fn write_header(&self, writer: impl io::Write) -> io::Result<()> { |
1259 | self.write(writer, true) |
1260 | } |
1261 | } |
1262 | diff --git a/src/dkim/mod.rs b/src/dkim/mod.rs |
1263 | index f68671e..11b5fe4 100644 |
1264 | --- a/src/dkim/mod.rs |
1265 | +++ b/src/dkim/mod.rs |
1266 | @@ -13,7 +13,7 @@ use std::borrow::Cow; |
1267 | use rsa::RsaPublicKey; |
1268 | |
1269 | use crate::{ |
1270 | - arc::Set, common::verify::VerifySignature, ARCOutput, DKIMOutput, DKIMResult, Error, Version, |
1271 | + arc::Set, common::verify::VerifySignature, ArcOutput, DkimOutput, DkimResult, Error, Version, |
1272 | }; |
1273 | |
1274 | pub mod canonicalize; |
1275 | @@ -177,10 +177,10 @@ impl<'x> VerifySignature for Signature<'x> { |
1276 | } |
1277 | } |
1278 | |
1279 | - impl<'x> DKIMOutput<'x> { |
1280 | + impl<'x> DkimOutput<'x> { |
1281 | pub(crate) fn pass() -> Self { |
1282 | - DKIMOutput { |
1283 | - result: DKIMResult::Pass, |
1284 | + DkimOutput { |
1285 | + result: DkimResult::Pass, |
1286 | signature: None, |
1287 | report: None, |
1288 | is_atps: false, |
1289 | @@ -188,8 +188,8 @@ impl<'x> DKIMOutput<'x> { |
1290 | } |
1291 | |
1292 | pub(crate) fn perm_err(err: Error) -> Self { |
1293 | - DKIMOutput { |
1294 | - result: DKIMResult::PermError(err), |
1295 | + DkimOutput { |
1296 | + result: DkimResult::PermError(err), |
1297 | signature: None, |
1298 | report: None, |
1299 | is_atps: false, |
1300 | @@ -197,8 +197,8 @@ impl<'x> DKIMOutput<'x> { |
1301 | } |
1302 | |
1303 | pub(crate) fn temp_err(err: Error) -> Self { |
1304 | - DKIMOutput { |
1305 | - result: DKIMResult::TempError(err), |
1306 | + DkimOutput { |
1307 | + result: DkimResult::TempError(err), |
1308 | signature: None, |
1309 | report: None, |
1310 | is_atps: false, |
1311 | @@ -206,8 +206,8 @@ impl<'x> DKIMOutput<'x> { |
1312 | } |
1313 | |
1314 | pub(crate) fn fail(err: Error) -> Self { |
1315 | - DKIMOutput { |
1316 | - result: DKIMResult::Fail(err), |
1317 | + DkimOutput { |
1318 | + result: DkimResult::Fail(err), |
1319 | signature: None, |
1320 | report: None, |
1321 | is_atps: false, |
1322 | @@ -215,8 +215,8 @@ impl<'x> DKIMOutput<'x> { |
1323 | } |
1324 | |
1325 | pub(crate) fn neutral(err: Error) -> Self { |
1326 | - DKIMOutput { |
1327 | - result: DKIMResult::Neutral(err), |
1328 | + DkimOutput { |
1329 | + result: DkimResult::Neutral(err), |
1330 | signature: None, |
1331 | report: None, |
1332 | is_atps: false, |
1333 | @@ -225,9 +225,9 @@ impl<'x> DKIMOutput<'x> { |
1334 | |
1335 | pub(crate) fn dns_error(err: Error) -> Self { |
1336 | if matches!(&err, Error::DNSError) { |
1337 | - DKIMOutput::temp_err(err) |
1338 | + DkimOutput::temp_err(err) |
1339 | } else { |
1340 | - DKIMOutput::perm_err(err) |
1341 | + DkimOutput::perm_err(err) |
1342 | } |
1343 | } |
1344 | |
1345 | @@ -241,7 +241,7 @@ impl<'x> DKIMOutput<'x> { |
1346 | self |
1347 | } |
1348 | |
1349 | - pub fn result(&self) -> &DKIMResult { |
1350 | + pub fn result(&self) -> &DkimResult { |
1351 | &self.result |
1352 | } |
1353 | |
1354 | @@ -254,8 +254,8 @@ impl<'x> DKIMOutput<'x> { |
1355 | } |
1356 | } |
1357 | |
1358 | - impl<'x> ARCOutput<'x> { |
1359 | - pub fn result(&self) -> &DKIMResult { |
1360 | + impl<'x> ArcOutput<'x> { |
1361 | + pub fn result(&self) -> &DkimResult { |
1362 | &self.result |
1363 | } |
1364 | |
1365 | @@ -264,12 +264,12 @@ impl<'x> ARCOutput<'x> { |
1366 | } |
1367 | } |
1368 | |
1369 | - impl From<Error> for DKIMResult { |
1370 | + impl From<Error> for DkimResult { |
1371 | fn from(err: Error) -> Self { |
1372 | if matches!(&err, Error::DNSError) { |
1373 | - DKIMResult::TempError(err) |
1374 | + DkimResult::TempError(err) |
1375 | } else { |
1376 | - DKIMResult::PermError(err) |
1377 | + DkimResult::PermError(err) |
1378 | } |
1379 | } |
1380 | } |
1381 | diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs |
1382 | index 4bd6c99..0c33add 100644 |
1383 | --- a/src/dkim/sign.rs |
1384 | +++ b/src/dkim/sign.rs |
1385 | @@ -8,7 +8,7 @@ |
1386 | * except according to those terms. |
1387 | */ |
1388 | |
1389 | - use std::{borrow::Cow, time::SystemTime}; |
1390 | + use std::{borrow::Cow, io, time::SystemTime}; |
1391 | |
1392 | use ed25519_dalek::Signer; |
1393 | use mail_builder::encoders::base64::base64_encode; |
1394 | @@ -89,7 +89,7 @@ impl<'x> Signature<'x> { |
1395 | |
1396 | fn sign_<T>(mut self, message: &'x [u8], with_key: &PrivateKey, now: u64) -> crate::Result<Self> |
1397 | where |
1398 | - T: Digest + AssociatedOid + std::io::Write, |
1399 | + T: Digest + AssociatedOid + io::Write, |
1400 | { |
1401 | let mut body_hasher = T::new(); |
1402 | let mut header_hasher = T::new(); |
1403 | @@ -218,7 +218,7 @@ mod test { |
1404 | use crate::{ |
1405 | common::parse::TxtRecordParser, |
1406 | dkim::{Atps, Canonicalization, DomainKey, DomainKeyReport, HashAlgorithm, Signature}, |
1407 | - AuthenticatedMessage, DKIMOutput, DKIMResult, PrivateKey, Resolver, |
1408 | + AuthenticatedMessage, DkimOutput, DkimResult, PrivateKey, Resolver, |
1409 | }; |
1410 | |
1411 | const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY----- |
1412 | @@ -497,7 +497,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1413 | signature: Signature<'x>, |
1414 | message_: &'x str, |
1415 | expect: Result<(), super::Error>, |
1416 | - ) -> Vec<DKIMOutput<'x>> { |
1417 | + ) -> Vec<DkimOutput<'x>> { |
1418 | let mut message = Vec::with_capacity(message_.len() + 100); |
1419 | signature.write(&mut message, true).unwrap(); |
1420 | message.extend_from_slice(message_.as_bytes()); |
1421 | @@ -506,16 +506,16 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1422 | let dkim = resolver.verify_dkim(&message).await; |
1423 | |
1424 | match (dkim.last().unwrap().result(), &expect) { |
1425 | - (DKIMResult::Pass, Ok(_)) => (), |
1426 | + (DkimResult::Pass, Ok(_)) => (), |
1427 | ( |
1428 | - DKIMResult::Fail(hdr) | DKIMResult::PermError(hdr) | DKIMResult::Neutral(hdr), |
1429 | + DkimResult::Fail(hdr) | DkimResult::PermError(hdr) | DkimResult::Neutral(hdr), |
1430 | Err(err), |
1431 | ) if hdr == err => (), |
1432 | (result, expect) => panic!("Expected {:?} but got {:?}.", expect, result), |
1433 | } |
1434 | |
1435 | dkim.into_iter() |
1436 | - .map(|d| DKIMOutput { |
1437 | + .map(|d| DkimOutput { |
1438 | result: d.result, |
1439 | signature: None, |
1440 | report: d.report, |
1441 | diff --git a/src/dkim/verify.rs b/src/dkim/verify.rs |
1442 | index 649a76c..3e91116 100644 |
1443 | --- a/src/dkim/verify.rs |
1444 | +++ b/src/dkim/verify.rs |
1445 | @@ -15,7 +15,7 @@ use sha2::Sha256; |
1446 | |
1447 | use crate::{ |
1448 | common::{base32::Base32Writer, verify::VerifySignature}, |
1449 | - is_within_pct, AuthenticatedMessage, DKIMOutput, DKIMResult, Error, Resolver, |
1450 | + is_within_pct, AuthenticatedMessage, DkimOutput, DkimResult, Error, Resolver, |
1451 | }; |
1452 | |
1453 | use super::{ |
1454 | @@ -29,7 +29,7 @@ impl Resolver { |
1455 | pub async fn verify_dkim<'x>( |
1456 | &self, |
1457 | message: &'x AuthenticatedMessage<'x>, |
1458 | - ) -> Vec<DKIMOutput<'x>> { |
1459 | + ) -> Vec<DkimOutput<'x>> { |
1460 | self.verify_dkim_( |
1461 | message, |
1462 | SystemTime::now() |
1463 | @@ -44,7 +44,7 @@ impl Resolver { |
1464 | &self, |
1465 | message: &'x AuthenticatedMessage<'x>, |
1466 | now: u64, |
1467 | - ) -> Vec<DKIMOutput<'x>> { |
1468 | + ) -> Vec<DkimOutput<'x>> { |
1469 | let mut output = Vec::with_capacity(message.dkim_headers.len()); |
1470 | let mut report_requested = false; |
1471 | |
1472 | @@ -61,13 +61,13 @@ impl Resolver { |
1473 | signature |
1474 | } else { |
1475 | output.push( |
1476 | - DKIMOutput::neutral(Error::SignatureExpired).with_signature(signature), |
1477 | + DkimOutput::neutral(Error::SignatureExpired).with_signature(signature), |
1478 | ); |
1479 | continue; |
1480 | } |
1481 | } |
1482 | Err(err) => { |
1483 | - output.push(DKIMOutput::neutral(err.clone())); |
1484 | + output.push(DkimOutput::neutral(err.clone())); |
1485 | continue; |
1486 | } |
1487 | }; |
1488 | @@ -83,7 +83,7 @@ impl Resolver { |
1489 | |
1490 | if bh != &signature.bh { |
1491 | output.push( |
1492 | - DKIMOutput::neutral(Error::FailedBodyHashMatch).with_signature(signature), |
1493 | + DkimOutput::neutral(Error::FailedBodyHashMatch).with_signature(signature), |
1494 | ); |
1495 | continue; |
1496 | } |
1497 | @@ -92,14 +92,14 @@ impl Resolver { |
1498 | let record = match self.txt_lookup::<DomainKey>(signature.domain_key()).await { |
1499 | Ok(record) => record, |
1500 | Err(err) => { |
1501 | - output.push(DKIMOutput::dns_error(err).with_signature(signature)); |
1502 | + output.push(DkimOutput::dns_error(err).with_signature(signature)); |
1503 | continue; |
1504 | } |
1505 | }; |
1506 | |
1507 | // Enforce t=s flag |
1508 | if !signature.validate_auid(&record) { |
1509 | - output.push(DKIMOutput::fail(Error::FailedAUIDMatch).with_signature(signature)); |
1510 | + output.push(DkimOutput::fail(Error::FailedAUIDMatch).with_signature(signature)); |
1511 | continue; |
1512 | } |
1513 | |
1514 | @@ -116,7 +116,7 @@ impl Resolver { |
1515 | |
1516 | // Verify signature |
1517 | if let Err(err) = signature.verify(record.as_ref(), &hh) { |
1518 | - output.push(DKIMOutput::fail(err).with_signature(signature)); |
1519 | + output.push(DkimOutput::fail(err).with_signature(signature)); |
1520 | continue; |
1521 | } |
1522 | |
1523 | @@ -158,11 +158,11 @@ impl Resolver { |
1524 | match self.txt_lookup::<Atps>(query_domain).await { |
1525 | Ok(_) => { |
1526 | // ATPS Verification successful |
1527 | - output.push(DKIMOutput::pass().with_atps().with_signature(signature)); |
1528 | + output.push(DkimOutput::pass().with_atps().with_signature(signature)); |
1529 | } |
1530 | Err(err) => { |
1531 | output.push( |
1532 | - DKIMOutput::dns_error(err) |
1533 | + DkimOutput::dns_error(err) |
1534 | .with_atps() |
1535 | .with_signature(signature), |
1536 | ); |
1537 | @@ -173,7 +173,7 @@ impl Resolver { |
1538 | } |
1539 | |
1540 | // Verification successful |
1541 | - output.push(DKIMOutput::pass().with_signature(signature)); |
1542 | + output.push(DkimOutput::pass().with_signature(signature)); |
1543 | } |
1544 | |
1545 | // Handle reports |
1546 | @@ -181,7 +181,7 @@ impl Resolver { |
1547 | for dkim in &mut output { |
1548 | // Process signatures with errors that requested reports |
1549 | let signature = if let Some(signature) = &dkim.signature { |
1550 | - if signature.r && dkim.result != DKIMResult::Pass { |
1551 | + if signature.r && dkim.result != DkimResult::Pass { |
1552 | signature |
1553 | } else { |
1554 | continue; |
1555 | @@ -206,10 +206,10 @@ impl Resolver { |
1556 | |
1557 | // Set report address |
1558 | dkim.report = match &dkim.result() { |
1559 | - DKIMResult::Neutral(err) |
1560 | - | DKIMResult::Fail(err) |
1561 | - | DKIMResult::PermError(err) |
1562 | - | DKIMResult::TempError(err) => { |
1563 | + DkimResult::Neutral(err) |
1564 | + | DkimResult::Fail(err) |
1565 | + | DkimResult::PermError(err) |
1566 | + | DkimResult::TempError(err) => { |
1567 | let send_report = match err { |
1568 | Error::CryptoError(_) |
1569 | | Error::Io(_) |
1570 | @@ -244,7 +244,7 @@ impl Resolver { |
1571 | None |
1572 | } |
1573 | } |
1574 | - DKIMResult::None | DKIMResult::Pass => None, |
1575 | + DkimResult::None | DkimResult::Pass => None, |
1576 | }; |
1577 | } |
1578 | } |
1579 | @@ -377,7 +377,7 @@ mod test { |
1580 | use crate::{ |
1581 | common::parse::TxtRecordParser, |
1582 | dkim::{verify::Verifier, DomainKey}, |
1583 | - AuthenticatedMessage, DKIMResult, Resolver, |
1584 | + AuthenticatedMessage, DkimResult, Resolver, |
1585 | }; |
1586 | |
1587 | #[tokio::test] |
1588 | @@ -401,7 +401,7 @@ mod test { |
1589 | |
1590 | let dkim = resolver.verify_dkim_(&message, 1667843664).await; |
1591 | |
1592 | - assert_eq!(dkim.last().unwrap().result(), &DKIMResult::Pass); |
1593 | + assert_eq!(dkim.last().unwrap().result(), &DkimResult::Pass); |
1594 | } |
1595 | } |
1596 | |
1597 | diff --git a/src/dmarc/mod.rs b/src/dmarc/mod.rs |
1598 | index 291bc2f..733ccc7 100644 |
1599 | --- a/src/dmarc/mod.rs |
1600 | +++ b/src/dmarc/mod.rs |
1601 | @@ -10,13 +10,13 @@ |
1602 | |
1603 | use std::{fmt::Display, sync::Arc}; |
1604 | |
1605 | - use crate::{DMARCOutput, DMARCResult, Error, Version}; |
1606 | + use crate::{DmarcOutput, DmarcResult, Error, Version}; |
1607 | |
1608 | pub mod parse; |
1609 | pub mod verify; |
1610 | |
1611 | #[derive(Debug, Clone, PartialEq, Eq)] |
1612 | - pub struct DMARC { |
1613 | + pub struct Dmarc { |
1614 | pub(crate) v: Version, |
1615 | pub(crate) adkim: Alignment, |
1616 | pub(crate) aspf: Alignment, |
1617 | @@ -92,45 +92,45 @@ impl URI { |
1618 | } |
1619 | } |
1620 | |
1621 | - impl From<Error> for DMARCResult { |
1622 | + impl From<Error> for DmarcResult { |
1623 | fn from(err: Error) -> Self { |
1624 | if matches!(&err, Error::DNSError) { |
1625 | - DMARCResult::TempError(err) |
1626 | + DmarcResult::TempError(err) |
1627 | } else { |
1628 | - DMARCResult::PermError(err) |
1629 | + DmarcResult::PermError(err) |
1630 | } |
1631 | } |
1632 | } |
1633 | |
1634 | - impl Default for DMARCOutput { |
1635 | + impl Default for DmarcOutput { |
1636 | fn default() -> Self { |
1637 | Self { |
1638 | domain: String::new(), |
1639 | policy: Policy::None, |
1640 | record: None, |
1641 | - spf_result: DMARCResult::None, |
1642 | - dkim_result: DMARCResult::None, |
1643 | + spf_result: DmarcResult::None, |
1644 | + dkim_result: DmarcResult::None, |
1645 | } |
1646 | } |
1647 | } |
1648 | |
1649 | - impl DMARCOutput { |
1650 | + impl DmarcOutput { |
1651 | pub(crate) fn with_domain(mut self, domain: &str) -> Self { |
1652 | self.domain = domain.to_string(); |
1653 | self |
1654 | } |
1655 | |
1656 | - pub(crate) fn with_spf_result(mut self, result: DMARCResult) -> Self { |
1657 | + pub(crate) fn with_spf_result(mut self, result: DmarcResult) -> Self { |
1658 | self.spf_result = result; |
1659 | self |
1660 | } |
1661 | |
1662 | - pub(crate) fn with_dkim_result(mut self, result: DMARCResult) -> Self { |
1663 | + pub(crate) fn with_dkim_result(mut self, result: DmarcResult) -> Self { |
1664 | self.dkim_result = result; |
1665 | self |
1666 | } |
1667 | |
1668 | - pub(crate) fn with_record(mut self, record: Arc<DMARC>) -> Self { |
1669 | + pub(crate) fn with_record(mut self, record: Arc<Dmarc>) -> Self { |
1670 | self.record = record.into(); |
1671 | self |
1672 | } |
1673 | @@ -143,15 +143,15 @@ impl DMARCOutput { |
1674 | self.policy |
1675 | } |
1676 | |
1677 | - pub fn dkim_result(&self) -> &DMARCResult { |
1678 | + pub fn dkim_result(&self) -> &DmarcResult { |
1679 | &self.dkim_result |
1680 | } |
1681 | |
1682 | - pub fn spf_result(&self) -> &DMARCResult { |
1683 | + pub fn spf_result(&self) -> &DmarcResult { |
1684 | &self.spf_result |
1685 | } |
1686 | |
1687 | - pub fn dmarc_record(&self) -> Option<&DMARC> { |
1688 | + pub fn dmarc_record(&self) -> Option<&Dmarc> { |
1689 | self.record.as_deref() |
1690 | } |
1691 | |
1692 | @@ -161,12 +161,12 @@ impl DMARCOutput { |
1693 | match &self.record { |
1694 | Some(record) |
1695 | if !record.ruf.is_empty() |
1696 | - && (self.dkim_result != DMARCResult::Pass |
1697 | + && (self.dkim_result != DmarcResult::Pass |
1698 | && matches!(record.fo, Report::Any | Report::Dkim | Report::DkimSpf)) |
1699 | - || (self.spf_result != DMARCResult::Pass |
1700 | + || (self.spf_result != DmarcResult::Pass |
1701 | && matches!(record.fo, Report::Any | Report::Spf | Report::DkimSpf)) |
1702 | - || (self.dkim_result != DMARCResult::Pass |
1703 | - && self.spf_result != DMARCResult::Pass |
1704 | + || (self.dkim_result != DmarcResult::Pass |
1705 | + && self.spf_result != DmarcResult::Pass |
1706 | && record.fo == Report::All) => |
1707 | { |
1708 | Some(record.fo.clone()) |
1709 | @@ -176,7 +176,7 @@ impl DMARCOutput { |
1710 | } |
1711 | } |
1712 | |
1713 | - impl DMARC { |
1714 | + impl Dmarc { |
1715 | pub fn ruf(&self) -> &[URI] { |
1716 | &self.ruf |
1717 | } |
1718 | diff --git a/src/dmarc/parse.rs b/src/dmarc/parse.rs |
1719 | index 7fd2e5f..45846f8 100644 |
1720 | --- a/src/dmarc/parse.rs |
1721 | +++ b/src/dmarc/parse.rs |
1722 | @@ -17,9 +17,9 @@ use crate::{ |
1723 | Error, Version, |
1724 | }; |
1725 | |
1726 | - use super::{Alignment, Format, Policy, Psd, Report, DMARC, URI}; |
1727 | + use super::{Alignment, Dmarc, Format, Policy, Psd, Report, URI}; |
1728 | |
1729 | - impl TxtRecordParser for DMARC { |
1730 | + impl TxtRecordParser for Dmarc { |
1731 | fn parse(bytes: &[u8]) -> crate::Result<Self> { |
1732 | let mut record = bytes.iter(); |
1733 | if record.key().unwrap_or(0) != V |
1734 | @@ -29,7 +29,7 @@ impl TxtRecordParser for DMARC { |
1735 | return Err(Error::InvalidRecordType); |
1736 | } |
1737 | |
1738 | - let mut dmarc = DMARC { |
1739 | + let mut dmarc = Dmarc { |
1740 | adkim: Alignment::Relaxed, |
1741 | aspf: Alignment::Relaxed, |
1742 | fo: Report::All, |
1743 | @@ -342,7 +342,7 @@ const PSD: u64 = (b'p' as u64) | (b's' as u64) << 8 | (b'd' as u64) << 16; |
1744 | mod test { |
1745 | use crate::{ |
1746 | common::parse::TxtRecordParser, |
1747 | - dmarc::{Alignment, Format, Policy, Psd, Report, DMARC, URI}, |
1748 | + dmarc::{Alignment, Dmarc, Format, Policy, Psd, Report, URI}, |
1749 | Version, |
1750 | }; |
1751 | |
1752 | @@ -351,7 +351,7 @@ mod test { |
1753 | for (record, expected_result) in [ |
1754 | ( |
1755 | "v=DMARC1; p=none; rua=mailto:dmarc-feedback@example.com", |
1756 | - DMARC { |
1757 | + Dmarc { |
1758 | adkim: Alignment::Relaxed, |
1759 | aspf: Alignment::Relaxed, |
1760 | fo: Report::All, |
1761 | @@ -373,7 +373,7 @@ mod test { |
1762 | "v=DMARC1; p=none; rua=mailto:dmarc-feedback@example.com;", |
1763 | "ruf=mailto:auth-reports@example.com" |
1764 | ), |
1765 | - DMARC { |
1766 | + Dmarc { |
1767 | adkim: Alignment::Relaxed, |
1768 | aspf: Alignment::Relaxed, |
1769 | fo: Report::All, |
1770 | @@ -395,7 +395,7 @@ mod test { |
1771 | "v=DMARC1; p=quarantine; rua=mailto:dmarc-feedback@example.com,", |
1772 | "mailto:tld-test@thirdparty.example.net!10m; pct=25; fo=d:s" |
1773 | ), |
1774 | - DMARC { |
1775 | + Dmarc { |
1776 | adkim: Alignment::Relaxed, |
1777 | aspf: Alignment::Relaxed, |
1778 | fo: Report::DkimSpf, |
1779 | @@ -420,7 +420,7 @@ mod test { |
1780 | "v=DMARC1; p=reject; sp=quarantine; np=None; aspf=s; adkim=s; fo = 1;", |
1781 | "rua=mailto:dmarc-feedback@example.com" |
1782 | ), |
1783 | - DMARC { |
1784 | + Dmarc { |
1785 | adkim: Alignment::Strict, |
1786 | aspf: Alignment::Strict, |
1787 | fo: Report::Any, |
1788 | @@ -443,7 +443,7 @@ mod test { |
1789 | "rua=mailto:dmarc-feedback@example.com!10 K , mailto:user%20@example.com ! 2G;", |
1790 | "ignore_me= true; fo=s; rf = AfrF; ", |
1791 | ), |
1792 | - DMARC { |
1793 | + Dmarc { |
1794 | adkim: Alignment::Relaxed, |
1795 | aspf: Alignment::Relaxed, |
1796 | fo: Report::Spf, |
1797 | @@ -468,7 +468,7 @@ mod test { |
1798 | "v=DMARC1; p=quarantine; rua=mailto:dmarc-feedback@example.com,", |
1799 | "mailto:tld-test@thirdparty.example.net; fo=s:d; t=y; psd=y;;", |
1800 | ), |
1801 | - DMARC { |
1802 | + Dmarc { |
1803 | adkim: Alignment::Relaxed, |
1804 | aspf: Alignment::Relaxed, |
1805 | fo: Report::DkimSpf, |
1806 | @@ -490,7 +490,7 @@ mod test { |
1807 | ), |
1808 | ] { |
1809 | assert_eq!( |
1810 | - DMARC::parse(record.as_bytes()) |
1811 | + Dmarc::parse(record.as_bytes()) |
1812 | .unwrap_or_else(|err| panic!("{:?} : {:?}", record, err)), |
1813 | expected_result, |
1814 | "{}", |
1815 | diff --git a/src/dmarc/verify.rs b/src/dmarc/verify.rs |
1816 | index 8c8d30c..c29d34c 100644 |
1817 | --- a/src/dmarc/verify.rs |
1818 | +++ b/src/dmarc/verify.rs |
1819 | @@ -11,20 +11,21 @@ |
1820 | use std::sync::Arc; |
1821 | |
1822 | use crate::{ |
1823 | - AuthenticatedMessage, DKIMOutput, DKIMResult, DMARCOutput, DMARCResult, Error, Resolver, |
1824 | - SPFOutput, SPFResult, |
1825 | + AuthenticatedMessage, DkimOutput, DkimResult, DmarcOutput, DmarcResult, Error, Resolver, |
1826 | + SpfOutput, SpfResult, |
1827 | }; |
1828 | |
1829 | - use super::{Alignment, DMARC, URI}; |
1830 | + use super::{Alignment, Dmarc, URI}; |
1831 | |
1832 | impl Resolver { |
1833 | + /// Verifies the DMARC policy of an RFC5322.From domain |
1834 | pub async fn verify_dmarc( |
1835 | &self, |
1836 | message: &AuthenticatedMessage<'_>, |
1837 | - dkim_output: &[DKIMOutput<'_>], |
1838 | + dkim_output: &[DkimOutput<'_>], |
1839 | mail_from_domain: &str, |
1840 | - spf_output: &SPFOutput, |
1841 | - ) -> DMARCOutput { |
1842 | + spf_output: &SpfOutput, |
1843 | + ) -> DmarcOutput { |
1844 | // Extract RFC5322.From |
1845 | let mut from_domain = ""; |
1846 | for from in &message.from { |
1847 | @@ -34,79 +35,79 @@ impl Resolver { |
1848 | } else if from_domain != domain { |
1849 | // Multi-valued RFC5322.From header fields with multiple |
1850 | // domains MUST be exempt from DMARC checking. |
1851 | - return DMARCOutput::default(); |
1852 | + return DmarcOutput::default(); |
1853 | } |
1854 | } |
1855 | } |
1856 | if from_domain.is_empty() { |
1857 | - return DMARCOutput::default(); |
1858 | + return DmarcOutput::default(); |
1859 | } |
1860 | |
1861 | // Obtain DMARC policy |
1862 | let dmarc = match self.dmarc_tree_walk(from_domain).await { |
1863 | Ok(Some(dmarc)) => dmarc, |
1864 | - Ok(None) => return DMARCOutput::default().with_domain(from_domain), |
1865 | + Ok(None) => return DmarcOutput::default().with_domain(from_domain), |
1866 | Err(err) => { |
1867 | - let err = DMARCResult::from(err); |
1868 | - return DMARCOutput::default() |
1869 | + let err = DmarcResult::from(err); |
1870 | + return DmarcOutput::default() |
1871 | .with_domain(from_domain) |
1872 | .with_dkim_result(err.clone()) |
1873 | .with_spf_result(err); |
1874 | } |
1875 | }; |
1876 | |
1877 | - let mut output = DMARCOutput { |
1878 | - spf_result: DMARCResult::None, |
1879 | - dkim_result: DMARCResult::None, |
1880 | + let mut output = DmarcOutput { |
1881 | + spf_result: DmarcResult::None, |
1882 | + dkim_result: DmarcResult::None, |
1883 | domain: from_domain.to_string(), |
1884 | policy: dmarc.p, |
1885 | record: None, |
1886 | }; |
1887 | |
1888 | - let has_dkim_pass = dkim_output.iter().any(|o| o.result == DKIMResult::Pass); |
1889 | - if spf_output.result == SPFResult::Pass || has_dkim_pass { |
1890 | + let has_dkim_pass = dkim_output.iter().any(|o| o.result == DkimResult::Pass); |
1891 | + if spf_output.result == SpfResult::Pass || has_dkim_pass { |
1892 | // Check SPF alignment |
1893 | let from_subdomain = format!(".{}", from_domain); |
1894 | - if spf_output.result == SPFResult::Pass { |
1895 | + if spf_output.result == SpfResult::Pass { |
1896 | output.spf_result = if mail_from_domain == from_domain { |
1897 | - DMARCResult::Pass |
1898 | + DmarcResult::Pass |
1899 | } else if dmarc.aspf == Alignment::Relaxed |
1900 | && mail_from_domain.ends_with(&from_subdomain) |
1901 | || from_domain.ends_with(&format!(".{}", mail_from_domain)) |
1902 | { |
1903 | output.policy = dmarc.sp; |
1904 | - DMARCResult::Pass |
1905 | + DmarcResult::Pass |
1906 | } else { |
1907 | - DMARCResult::Fail(Error::DMARCNotAligned) |
1908 | + DmarcResult::Fail(Error::DMARCNotAligned) |
1909 | }; |
1910 | } |
1911 | |
1912 | // Check DKIM alignment |
1913 | if has_dkim_pass { |
1914 | output.dkim_result = if dkim_output.iter().any(|o| { |
1915 | - o.result == DKIMResult::Pass && o.signature.as_ref().unwrap().d.eq(from_domain) |
1916 | + o.result == DkimResult::Pass && o.signature.as_ref().unwrap().d.eq(from_domain) |
1917 | }) { |
1918 | - DMARCResult::Pass |
1919 | + DmarcResult::Pass |
1920 | } else if dmarc.adkim == Alignment::Relaxed |
1921 | && dkim_output.iter().any(|o| { |
1922 | - o.result == DKIMResult::Pass |
1923 | + o.result == DkimResult::Pass |
1924 | && (o.signature.as_ref().unwrap().d.ends_with(&from_subdomain) |
1925 | || from_domain |
1926 | .ends_with(&format!(".{}", o.signature.as_ref().unwrap().d))) |
1927 | }) |
1928 | { |
1929 | output.policy = dmarc.sp; |
1930 | - DMARCResult::Pass |
1931 | + DmarcResult::Pass |
1932 | } else { |
1933 | if dkim_output.iter().any(|o| { |
1934 | - o.result == DKIMResult::Pass |
1935 | + o.result == DkimResult::Pass |
1936 | && (o.signature.as_ref().unwrap().d.ends_with(&from_subdomain) |
1937 | || from_domain |
1938 | .ends_with(&format!(".{}", o.signature.as_ref().unwrap().d))) |
1939 | }) { |
1940 | output.policy = dmarc.sp; |
1941 | } |
1942 | - DMARCResult::Fail(Error::DMARCNotAligned) |
1943 | + DmarcResult::Fail(Error::DMARCNotAligned) |
1944 | }; |
1945 | } |
1946 | } |
1947 | @@ -114,6 +115,7 @@ impl Resolver { |
1948 | output.with_record(dmarc) |
1949 | } |
1950 | |
1951 | + /// Validates the external report e-mail addresses of a DMARC record |
1952 | pub async fn verify_dmarc_report_address<'x>( |
1953 | &self, |
1954 | domain: &str, |
1955 | @@ -123,7 +125,7 @@ impl Resolver { |
1956 | for address in addresses { |
1957 | if address.uri.ends_with(domain) |
1958 | || match self |
1959 | - .txt_lookup::<DMARC>(format!( |
1960 | + .txt_lookup::<Dmarc>(format!( |
1961 | "{}.report.dmarc.{}.", |
1962 | domain, |
1963 | address |
1964 | @@ -146,7 +148,7 @@ impl Resolver { |
1965 | result.into() |
1966 | } |
1967 | |
1968 | - async fn dmarc_tree_walk(&self, domain: &str) -> crate::Result<Option<Arc<DMARC>>> { |
1969 | + async fn dmarc_tree_walk(&self, domain: &str) -> crate::Result<Option<Arc<Dmarc>>> { |
1970 | let labels = domain.split('.').collect::<Vec<_>>(); |
1971 | let mut x = labels.len(); |
1972 | if x == 1 { |
1973 | @@ -163,7 +165,7 @@ impl Resolver { |
1974 | domain.push('.'); |
1975 | |
1976 | // Query DMARC |
1977 | - match self.txt_lookup::<DMARC>(domain).await { |
1978 | + match self.txt_lookup::<Dmarc>(domain).await { |
1979 | Ok(dmarc) => { |
1980 | return Ok(Some(dmarc)); |
1981 | } |
1982 | @@ -192,9 +194,9 @@ mod test { |
1983 | use crate::{ |
1984 | common::parse::TxtRecordParser, |
1985 | dkim::Signature, |
1986 | - dmarc::{Policy, DMARC, URI}, |
1987 | - AuthenticatedMessage, DKIMOutput, DKIMResult, DMARCResult, Error, Resolver, SPFOutput, |
1988 | - SPFResult, |
1989 | + dmarc::{Dmarc, Policy, URI}, |
1990 | + AuthenticatedMessage, DkimOutput, DkimResult, DmarcResult, Error, Resolver, SpfOutput, |
1991 | + SpfResult, |
1992 | }; |
1993 | |
1994 | #[tokio::test] |
1995 | @@ -223,10 +225,10 @@ mod test { |
1996 | "From: hello@example.org\r\n\r\n", |
1997 | "example.org", |
1998 | "example.org", |
1999 | - DKIMResult::Pass, |
2000 | - SPFResult::Pass, |
2001 | - DMARCResult::Pass, |
2002 | - DMARCResult::Pass, |
2003 | + DkimResult::Pass, |
2004 | + SpfResult::Pass, |
2005 | + DmarcResult::Pass, |
2006 | + DmarcResult::Pass, |
2007 | Policy::Reject, |
2008 | ), |
2009 | // Relaxed - Pass |
2010 | @@ -239,10 +241,10 @@ mod test { |
2011 | "From: hello@example.org\r\n\r\n", |
2012 | "subdomain.example.org", |
2013 | "subdomain.example.org", |
2014 | - DKIMResult::Pass, |
2015 | - SPFResult::Pass, |
2016 | - DMARCResult::Pass, |
2017 | - DMARCResult::Pass, |
2018 | + DkimResult::Pass, |
2019 | + SpfResult::Pass, |
2020 | + DmarcResult::Pass, |
2021 | + DmarcResult::Pass, |
2022 | Policy::Quarantine, |
2023 | ), |
2024 | // Strict - Fail |
2025 | @@ -255,10 +257,10 @@ mod test { |
2026 | "From: hello@example.org\r\n\r\n", |
2027 | "subdomain.example.org", |
2028 | "subdomain.example.org", |
2029 | - DKIMResult::Pass, |
2030 | - SPFResult::Pass, |
2031 | - DMARCResult::Fail(Error::DMARCNotAligned), |
2032 | - DMARCResult::Fail(Error::DMARCNotAligned), |
2033 | + DkimResult::Pass, |
2034 | + SpfResult::Pass, |
2035 | + DmarcResult::Fail(Error::DMARCNotAligned), |
2036 | + DmarcResult::Fail(Error::DMARCNotAligned), |
2037 | Policy::Quarantine, |
2038 | ), |
2039 | // Strict - Pass with tree walk |
2040 | @@ -271,10 +273,10 @@ mod test { |
2041 | "From: hello@a.b.c.example.org\r\n\r\n", |
2042 | "a.b.c.example.org", |
2043 | "a.b.c.example.org", |
2044 | - DKIMResult::Pass, |
2045 | - SPFResult::Pass, |
2046 | - DMARCResult::Pass, |
2047 | - DMARCResult::Pass, |
2048 | + DkimResult::Pass, |
2049 | + SpfResult::Pass, |
2050 | + DmarcResult::Pass, |
2051 | + DmarcResult::Pass, |
2052 | Policy::Reject, |
2053 | ), |
2054 | // Relaxed - Pass with tree walk |
2055 | @@ -287,10 +289,10 @@ mod test { |
2056 | "From: hello@a.b.c.example.org\r\n\r\n", |
2057 | "example.org", |
2058 | "example.org", |
2059 | - DKIMResult::Pass, |
2060 | - SPFResult::Pass, |
2061 | - DMARCResult::Pass, |
2062 | - DMARCResult::Pass, |
2063 | + DkimResult::Pass, |
2064 | + SpfResult::Pass, |
2065 | + DmarcResult::Pass, |
2066 | + DmarcResult::Pass, |
2067 | Policy::Quarantine, |
2068 | ), |
2069 | // Failed mechanisms |
2070 | @@ -303,16 +305,16 @@ mod test { |
2071 | "From: hello@example.org\r\n\r\n", |
2072 | "example.org", |
2073 | "example.org", |
2074 | - DKIMResult::Fail(Error::SignatureExpired), |
2075 | - SPFResult::Fail, |
2076 | - DMARCResult::None, |
2077 | - DMARCResult::None, |
2078 | + DkimResult::Fail(Error::SignatureExpired), |
2079 | + SpfResult::Fail, |
2080 | + DmarcResult::None, |
2081 | + DmarcResult::None, |
2082 | Policy::Reject, |
2083 | ), |
2084 | ] { |
2085 | resolver.txt_add( |
2086 | dmarc_dns, |
2087 | - DMARC::parse(dmarc.as_bytes()).unwrap(), |
2088 | + Dmarc::parse(dmarc.as_bytes()).unwrap(), |
2089 | Instant::now() + Duration::new(3200, 0), |
2090 | ); |
2091 | |
2092 | @@ -321,13 +323,13 @@ mod test { |
2093 | d: signature_domain.into(), |
2094 | ..Default::default() |
2095 | }; |
2096 | - let dkim = DKIMOutput { |
2097 | + let dkim = DkimOutput { |
2098 | result: dkim, |
2099 | signature: (&signature).into(), |
2100 | report: None, |
2101 | is_atps: false, |
2102 | }; |
2103 | - let spf = SPFOutput { |
2104 | + let spf = SpfOutput { |
2105 | result: spf, |
2106 | domain: mail_from_domain.to_string(), |
2107 | report: None, |
2108 | @@ -347,7 +349,7 @@ mod test { |
2109 | let resolver = Resolver::new_system_conf().unwrap(); |
2110 | resolver.txt_add( |
2111 | "example.org.report.dmarc.external.org.", |
2112 | - DMARC::parse(b"v=DMARC1").unwrap(), |
2113 | + Dmarc::parse(b"v=DMARC1").unwrap(), |
2114 | Instant::now() + Duration::new(3200, 0), |
2115 | ); |
2116 | let uris = vec![ |
2117 | diff --git a/src/lib.rs b/src/lib.rs |
2118 | index 39825b1..b655369 100644 |
2119 | --- a/src/lib.rs |
2120 | +++ b/src/lib.rs |
2121 | @@ -53,7 +53,7 @@ |
2122 | //! let result = resolver.verify_dkim(&authenticated_message).await; |
2123 | //! |
2124 | //! // Make sure all signatures passed verification |
2125 | - //! assert!(result.iter().all(|s| s.result() == &DKIMResult::Pass)); |
2126 | + //! assert!(result.iter().all(|s| s.result() == &DkimResult::Pass)); |
2127 | //! ``` |
2128 | //! |
2129 | //! ### DKIM Signing |
2130 | @@ -103,7 +103,7 @@ |
2131 | //! let result = resolver.verify_arc(&authenticated_message).await; |
2132 | //! |
2133 | //! // Make sure ARC passed verification |
2134 | - //! assert_eq!(result.result(), &DKIMResult::Pass); |
2135 | + //! assert_eq!(result.result(), &DkimResult::Pass); |
2136 | //! ``` |
2137 | //! |
2138 | //! ### ARC Chain Sealing |
2139 | @@ -128,7 +128,7 @@ |
2140 | //! if arc_result.can_be_sealed() { |
2141 | //! // Seal the e-mail message using RSA-SHA256 |
2142 | //! let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
2143 | - //! let arc_set = ARC::new(&auth_results) |
2144 | + //! let arc_set = ArcSet::new(&auth_results) |
2145 | //! .domain("example.org") |
2146 | //! .selector("default") |
2147 | //! .headers(["From", "To", "Subject", "DKIM-Signature"]) |
2148 | @@ -152,13 +152,13 @@ |
2149 | //! let result = resolver |
2150 | //! .verify_spf_helo("127.0.0.1".parse().unwrap(), "gmail.com") |
2151 | //! .await; |
2152 | - //! assert_eq!(result.result(), SPFResult::Fail); |
2153 | + //! assert_eq!(result.result(), SpfResult::Fail); |
2154 | //! |
2155 | //! // Verify MAIL-FROM identity |
2156 | //! let result = resolver |
2157 | //! .verify_spf_sender("::1".parse().unwrap(), "gmail.com", "sender@gmail.com") |
2158 | //! .await; |
2159 | - //! assert_eq!(result.result(), SPFResult::Fail); |
2160 | + //! assert_eq!(result.result(), SpfResult::Fail); |
2161 | //! ``` |
2162 | //! |
2163 | //! ### DMARC Policy Evaluation |
2164 | @@ -185,8 +185,8 @@ |
2165 | //! &spf_result, |
2166 | //! ) |
2167 | //! .await; |
2168 | - //! assert_eq!(dmarc_result.dkim_result(), &DMARCResult::Pass); |
2169 | - //! assert_eq!(dmarc_result.spf_result(), &DMARCResult::Pass); |
2170 | + //! assert_eq!(dmarc_result.dkim_result(), &DmarcResult::Pass); |
2171 | + //! assert_eq!(dmarc_result.spf_result(), &DmarcResult::Pass); |
2172 | //! ``` |
2173 | //! |
2174 | //! More examples available under the [examples](examples) directory. |
2175 | @@ -202,7 +202,7 @@ |
2176 | //! To fuzz the library with `cargo-fuzz`: |
2177 | //! |
2178 | //! ```bash |
2179 | - //! $ cargo +nightly fuzz run mail_parser |
2180 | + //! $ cargo +nightly fuzz run mail_auth |
2181 | //! ``` |
2182 | //! |
2183 | //! ## Conformed RFCs |
2184 | @@ -255,6 +255,7 @@ |
2185 | use std::{ |
2186 | cell::Cell, |
2187 | fmt::Display, |
2188 | + io, |
2189 | net::{IpAddr, Ipv4Addr, Ipv6Addr}, |
2190 | sync::Arc, |
2191 | time::SystemTime, |
2192 | @@ -263,9 +264,9 @@ use std::{ |
2193 | use arc::Set; |
2194 | use common::{headers::Header, lru::LruCache}; |
2195 | use dkim::{Atps, Canonicalization, DomainKey, DomainKeyReport, HashAlgorithm}; |
2196 | - use dmarc::DMARC; |
2197 | + use dmarc::Dmarc; |
2198 | use rsa::RsaPrivateKey; |
2199 | - use spf::{Macro, SPF}; |
2200 | + use spf::{Macro, Spf}; |
2201 | use trust_dns_resolver::{proto::op::ResponseCode, TokioAsyncResolver}; |
2202 | |
2203 | pub mod arc; |
2204 | @@ -294,13 +295,12 @@ pub struct Resolver { |
2205 | } |
2206 | |
2207 | #[derive(Debug, Clone, PartialEq, Eq)] |
2208 | - #[allow(clippy::upper_case_acronyms)] |
2209 | pub(crate) enum Txt { |
2210 | - SPF(Arc<SPF>), |
2211 | - SPFMacro(Arc<Macro>), |
2212 | + Spf(Arc<Spf>), |
2213 | + SpfMacro(Arc<Macro>), |
2214 | DomainKey(Arc<DomainKey>), |
2215 | DomainKeyReport(Arc<DomainKeyReport>), |
2216 | - DMARC(Arc<DMARC>), |
2217 | + Dmarc(Arc<Dmarc>), |
2218 | Atps(Arc<Atps>), |
2219 | Error(Error), |
2220 | } |
2221 | @@ -339,12 +339,12 @@ pub struct AuthenticationResults<'x> { |
2222 | |
2223 | #[derive(Debug, Clone, PartialEq, Eq)] |
2224 | // Received-SPF header |
2225 | - pub struct ReceivedSPF { |
2226 | + pub struct ReceivedSpf { |
2227 | pub(crate) received_spf: String, |
2228 | } |
2229 | |
2230 | #[derive(Debug, PartialEq, Eq, Clone)] |
2231 | - pub enum DKIMResult { |
2232 | + pub enum DkimResult { |
2233 | Pass, |
2234 | Neutral(crate::Error), |
2235 | Fail(crate::Error), |
2236 | @@ -354,21 +354,21 @@ pub enum DKIMResult { |
2237 | } |
2238 | |
2239 | #[derive(Debug, PartialEq, Eq, Clone)] |
2240 | - pub struct DKIMOutput<'x> { |
2241 | - result: DKIMResult, |
2242 | + pub struct DkimOutput<'x> { |
2243 | + result: DkimResult, |
2244 | signature: Option<&'x dkim::Signature<'x>>, |
2245 | report: Option<String>, |
2246 | is_atps: bool, |
2247 | } |
2248 | |
2249 | #[derive(Debug, PartialEq, Eq, Clone)] |
2250 | - pub struct ARCOutput<'x> { |
2251 | - result: DKIMResult, |
2252 | + pub struct ArcOutput<'x> { |
2253 | + result: DkimResult, |
2254 | set: Vec<Set<'x>>, |
2255 | } |
2256 | |
2257 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
2258 | - pub enum SPFResult { |
2259 | + pub enum SpfResult { |
2260 | Pass, |
2261 | Fail, |
2262 | SoftFail, |
2263 | @@ -379,24 +379,24 @@ pub enum SPFResult { |
2264 | } |
2265 | |
2266 | #[derive(Debug, PartialEq, Eq, Clone)] |
2267 | - pub struct SPFOutput { |
2268 | - result: SPFResult, |
2269 | + pub struct SpfOutput { |
2270 | + result: SpfResult, |
2271 | domain: String, |
2272 | report: Option<String>, |
2273 | explanation: Option<String>, |
2274 | } |
2275 | |
2276 | #[derive(Debug, PartialEq, Eq, Clone)] |
2277 | - pub struct DMARCOutput { |
2278 | - spf_result: DMARCResult, |
2279 | - dkim_result: DMARCResult, |
2280 | + pub struct DmarcOutput { |
2281 | + spf_result: DmarcResult, |
2282 | + dkim_result: DmarcResult, |
2283 | domain: String, |
2284 | policy: dmarc::Policy, |
2285 | - record: Option<Arc<DMARC>>, |
2286 | + record: Option<Arc<Dmarc>>, |
2287 | } |
2288 | |
2289 | #[derive(Debug, PartialEq, Eq, Clone)] |
2290 | - pub enum DMARCResult { |
2291 | + pub enum DmarcResult { |
2292 | Pass, |
2293 | Fail(crate::Error), |
2294 | TempError(crate::Error), |
2295 | @@ -453,42 +453,42 @@ impl Display for Error { |
2296 | Error::CryptoError(err) => write!(f, "Cryptography layer error: {}", err), |
2297 | Error::Io(e) => write!(f, "I/O error: {}", e), |
2298 | Error::Base64 => write!(f, "Base64 encode or decode error."), |
2299 | - Error::UnsupportedVersion => write!(f, "Unsupported version in DKIM Signature."), |
2300 | - Error::UnsupportedAlgorithm => write!(f, "Unsupported algorithm in DKIM Signature."), |
2301 | + Error::UnsupportedVersion => write!(f, "Unsupported version in DKIM Signature"), |
2302 | + Error::UnsupportedAlgorithm => write!(f, "Unsupported algorithm in DKIM Signature"), |
2303 | Error::UnsupportedCanonicalization => { |
2304 | - write!(f, "Unsupported canonicalization method in DKIM Signature.") |
2305 | + write!(f, "Unsupported canonicalization method in DKIM Signature") |
2306 | } |
2307 | Error::UnsupportedKeyType => { |
2308 | - write!(f, "Unsupported key type in DKIM DNS record.") |
2309 | + write!(f, "Unsupported key type in DKIM DNS record") |
2310 | } |
2311 | Error::FailedBodyHashMatch => { |
2312 | - write!(f, "Calculated body hash does not match signature hash.") |
2313 | + write!(f, "Calculated body hash does not match signature hash") |
2314 | } |
2315 | - Error::RevokedPublicKey => write!(f, "Public key for this signature has been revoked."), |
2316 | + Error::RevokedPublicKey => write!(f, "Public key for this signature has been revoked"), |
2317 | Error::IncompatibleAlgorithms => write!( |
2318 | f, |
2319 | - "Incompatible algorithms used in signature and DKIM DNS record." |
2320 | + "Incompatible algorithms used in signature and DKIM DNS record" |
2321 | ), |
2322 | - Error::FailedVerification => write!(f, "Signature verification failed."), |
2323 | - Error::SignatureExpired => write!(f, "Signature expired."), |
2324 | - Error::FailedAUIDMatch => write!(f, "AUID does not match domain name."), |
2325 | + Error::FailedVerification => write!(f, "Signature verification failed"), |
2326 | + Error::SignatureExpired => write!(f, "Signature expired"), |
2327 | + Error::FailedAUIDMatch => write!(f, "AUID does not match domain name"), |
2328 | Error::ARCInvalidInstance(i) => { |
2329 | - write!(f, "Invalid 'i={}' value found in ARC header.", i) |
2330 | + write!(f, "Invalid 'i={}' value found in ARC header", i) |
2331 | } |
2332 | - Error::ARCInvalidCV => write!(f, "Invalid 'cv=' value found in ARC header."), |
2333 | - Error::ARCHasHeaderTag => write!(f, "Invalid 'h=' tag present in ARC-Seal."), |
2334 | - Error::ARCBrokenChain => write!(f, "Broken or missing ARC chain."), |
2335 | - Error::ARCChainTooLong => write!(f, "Too many ARC headers."), |
2336 | - Error::InvalidRecordType => write!(f, "Invalid record."), |
2337 | - Error::DNSError => write!(f, "DNS resolution error."), |
2338 | - Error::DNSRecordNotFound(code) => write!(f, "DNS record not found: {}.", code), |
2339 | - Error::DMARCNotAligned => write!(f, "DMARC policy not aligned."), |
2340 | + Error::ARCInvalidCV => write!(f, "Invalid 'cv=' value found in ARC header"), |
2341 | + Error::ARCHasHeaderTag => write!(f, "Invalid 'h=' tag present in ARC-Seal"), |
2342 | + Error::ARCBrokenChain => write!(f, "Broken or missing ARC chain"), |
2343 | + Error::ARCChainTooLong => write!(f, "Too many ARC headers"), |
2344 | + Error::InvalidRecordType => write!(f, "Invalid record"), |
2345 | + Error::DNSError => write!(f, "DNS resolution error"), |
2346 | + Error::DNSRecordNotFound(code) => write!(f, "DNS record not found: {}", code), |
2347 | + Error::DMARCNotAligned => write!(f, "DMARC policy not aligned"), |
2348 | } |
2349 | } |
2350 | } |
2351 | |
2352 | - impl From<std::io::Error> for Error { |
2353 | - fn from(err: std::io::Error) -> Self { |
2354 | + impl From<io::Error> for Error { |
2355 | + fn from(err: io::Error) -> Self { |
2356 | Error::Io(err.to_string()) |
2357 | } |
2358 | } |
2359 | @@ -507,6 +507,9 @@ impl From<ed25519_dalek::ed25519::Error> for Error { |
2360 | |
2361 | thread_local!(static COUNTER: Cell<u64> = Cell::new(0)); |
2362 | |
2363 | + /// Generates a random value between 0 and 100. |
2364 | + /// Returns true if the generated value is within the requested |
2365 | + /// sampling percentage specified in a SPF, DKIM or DMARC policy. |
2366 | pub(crate) fn is_within_pct(pct: u8) -> bool { |
2367 | pct == 100 |
2368 | || COUNTER.with(|c| { |
2369 | diff --git a/src/report/arf/generate.rs b/src/report/arf/generate.rs |
2370 | index df00b3d..75a2309 100644 |
2371 | --- a/src/report/arf/generate.rs |
2372 | +++ b/src/report/arf/generate.rs |
2373 | @@ -8,7 +8,7 @@ |
2374 | * except according to those terms. |
2375 | */ |
2376 | |
2377 | - use std::{fmt::Write, time::SystemTime}; |
2378 | + use std::{fmt::Write, io, time::SystemTime}; |
2379 | |
2380 | use mail_builder::{ |
2381 | headers::{content_type::ContentType, HeaderType}, |
2382 | @@ -25,8 +25,8 @@ impl<'x> Feedback<'x> { |
2383 | from: &'x str, |
2384 | to: &'x str, |
2385 | subject: &'x str, |
2386 | - writer: impl std::io::Write, |
2387 | - ) -> std::io::Result<()> { |
2388 | + writer: impl io::Write, |
2389 | + ) -> io::Result<()> { |
2390 | // Generate ARF |
2391 | |
2392 | let arf = self.as_arf(); |
2393 | @@ -101,10 +101,10 @@ impl<'x> Feedback<'x> { |
2394 | .write_to(writer) |
2395 | } |
2396 | |
2397 | - pub fn as_rfc5322(&self, from: &str, to: &str, subject: &str) -> std::io::Result<String> { |
2398 | + pub fn as_rfc5322(&self, from: &str, to: &str, subject: &str) -> io::Result<String> { |
2399 | let mut buf = Vec::new(); |
2400 | self.write_rfc5322(from, to, subject, &mut buf)?; |
2401 | - String::from_utf8(buf).map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) |
2402 | + String::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err)) |
2403 | } |
2404 | |
2405 | pub fn as_arf(&self) -> String { |
2406 | diff --git a/src/report/dmarc/generate.rs b/src/report/dmarc/generate.rs |
2407 | index d44b026..e35f00e 100644 |
2408 | --- a/src/report/dmarc/generate.rs |
2409 | +++ b/src/report/dmarc/generate.rs |
2410 | @@ -12,14 +12,15 @@ use flate2::{write::GzEncoder, Compression}; |
2411 | use mail_builder::{headers::HeaderType, MessageBuilder}; |
2412 | |
2413 | use crate::report::{ |
2414 | - ActionDisposition, Alignment, AuthResult, DKIMAuthResult, DKIMResult, DMARCResult, DateRange, |
2415 | - Disposition, Identifier, PolicyEvaluated, PolicyOverride, PolicyOverrideReason, |
2416 | - PolicyPublished, Record, Report, ReportMetadata, Row, SPFAuthResult, SPFDomainScope, SPFResult, |
2417 | + ActionDisposition, Alignment, AuthResult, DKIMAuthResult, DateRange, Disposition, DkimResult, |
2418 | + DmarcResult, Identifier, PolicyEvaluated, PolicyOverride, PolicyOverrideReason, |
2419 | + PolicyPublished, Record, Report, ReportMetadata, Row, SPFAuthResult, SPFDomainScope, SpfResult, |
2420 | }; |
2421 | |
2422 | use std::{ |
2423 | borrow::Cow, |
2424 | fmt::{Display, Formatter, Write}, |
2425 | + io, |
2426 | }; |
2427 | |
2428 | impl Report { |
2429 | @@ -29,12 +30,12 @@ impl Report { |
2430 | submitter: &'x str, |
2431 | from: &'x str, |
2432 | to: &'x str, |
2433 | - writer: impl std::io::Write, |
2434 | - ) -> std::io::Result<()> { |
2435 | + writer: impl io::Write, |
2436 | + ) -> io::Result<()> { |
2437 | // Compress XML report |
2438 | let xml = self.as_xnl(); |
2439 | let mut e = GzEncoder::new(Vec::with_capacity(xml.len()), Compression::default()); |
2440 | - std::io::Write::write_all(&mut e, xml.as_bytes())?; |
2441 | + io::Write::write_all(&mut e, xml.as_bytes())?; |
2442 | let compressed_bytes = e.finish()?; |
2443 | |
2444 | MessageBuilder::new() |
2445 | @@ -79,10 +80,10 @@ impl Report { |
2446 | submitter: &'x str, |
2447 | from: &'x str, |
2448 | to: &'x str, |
2449 | - ) -> std::io::Result<String> { |
2450 | + ) -> io::Result<String> { |
2451 | let mut buf = Vec::new(); |
2452 | self.write_rfc5322(receiver_domain, submitter, from, to, &mut buf)?; |
2453 | - String::from_utf8(buf).map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) |
2454 | + String::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err)) |
2455 | } |
2456 | |
2457 | pub fn as_xnl(&self) -> String { |
2458 | @@ -324,12 +325,12 @@ impl Display for ActionDisposition { |
2459 | } |
2460 | } |
2461 | |
2462 | - impl Display for DMARCResult { |
2463 | + impl Display for DmarcResult { |
2464 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
2465 | f.write_str(match self { |
2466 | - DMARCResult::Pass => "pass", |
2467 | - DMARCResult::Fail => "fail", |
2468 | - DMARCResult::Unspecified => "", |
2469 | + DmarcResult::Pass => "pass", |
2470 | + DmarcResult::Fail => "fail", |
2471 | + DmarcResult::Unspecified => "", |
2472 | }) |
2473 | } |
2474 | } |
2475 | @@ -347,16 +348,16 @@ impl Display for PolicyOverride { |
2476 | } |
2477 | } |
2478 | |
2479 | - impl Display for DKIMResult { |
2480 | + impl Display for DkimResult { |
2481 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
2482 | f.write_str(match self { |
2483 | - DKIMResult::None => "none", |
2484 | - DKIMResult::Pass => "pass", |
2485 | - DKIMResult::Fail => "fail", |
2486 | - DKIMResult::Policy => "policy", |
2487 | - DKIMResult::Neutral => "neutral", |
2488 | - DKIMResult::TempError => "temperror", |
2489 | - DKIMResult::PermError => "permerror", |
2490 | + DkimResult::None => "none", |
2491 | + DkimResult::Pass => "pass", |
2492 | + DkimResult::Fail => "fail", |
2493 | + DkimResult::Policy => "policy", |
2494 | + DkimResult::Neutral => "neutral", |
2495 | + DkimResult::TempError => "temperror", |
2496 | + DkimResult::PermError => "permerror", |
2497 | }) |
2498 | } |
2499 | } |
2500 | @@ -370,16 +371,16 @@ impl Display for SPFDomainScope { |
2501 | } |
2502 | } |
2503 | |
2504 | - impl Display for SPFResult { |
2505 | + impl Display for SpfResult { |
2506 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
2507 | f.write_str(match self { |
2508 | - SPFResult::None => "none", |
2509 | - SPFResult::Neutral => "neutral", |
2510 | - SPFResult::Pass => "pass", |
2511 | - SPFResult::Fail => "fail", |
2512 | - SPFResult::SoftFail => "softfail", |
2513 | - SPFResult::TempError => "temperror", |
2514 | - SPFResult::PermError => "permerror", |
2515 | + SpfResult::None => "none", |
2516 | + SpfResult::Neutral => "neutral", |
2517 | + SpfResult::Pass => "pass", |
2518 | + SpfResult::Fail => "fail", |
2519 | + SpfResult::SoftFail => "softfail", |
2520 | + SpfResult::TempError => "temperror", |
2521 | + SpfResult::PermError => "permerror", |
2522 | }) |
2523 | } |
2524 | } |
2525 | @@ -420,9 +421,9 @@ fn escape_xml(text: &str) -> Cow<'_, str> { |
2526 | #[cfg(test)] |
2527 | mod test { |
2528 | use crate::report::{ |
2529 | - ActionDisposition, Alignment, DKIMAuthResult, DKIMResult, DMARCResult, Disposition, |
2530 | + ActionDisposition, Alignment, DKIMAuthResult, Disposition, DkimResult, DmarcResult, |
2531 | PolicyOverride, PolicyOverrideReason, Record, Report, SPFAuthResult, SPFDomainScope, |
2532 | - SPFResult, |
2533 | + SpfResult, |
2534 | }; |
2535 | |
2536 | #[test] |
2537 | @@ -448,8 +449,8 @@ mod test { |
2538 | .with_source_ip("192.168.1.2".parse().unwrap()) |
2539 | .with_count(3) |
2540 | .with_action_disposition(ActionDisposition::Pass) |
2541 | - .with_dmarc_dkim_result(DMARCResult::Pass) |
2542 | - .with_dmarc_spf_result(DMARCResult::Fail) |
2543 | + .with_dmarc_dkim_result(DmarcResult::Pass) |
2544 | + .with_dmarc_spf_result(DmarcResult::Fail) |
2545 | .with_policy_override_reason( |
2546 | PolicyOverrideReason::new(PolicyOverride::Forwarded) |
2547 | .with_comment("it was forwarded"), |
2548 | @@ -465,14 +466,14 @@ mod test { |
2549 | DKIMAuthResult::new() |
2550 | .with_domain("test.org") |
2551 | .with_selector("my-selector") |
2552 | - .with_result(DKIMResult::PermError) |
2553 | + .with_result(DkimResult::PermError) |
2554 | .with_human_result("failed to parse record"), |
2555 | ) |
2556 | .with_spf_auth_result( |
2557 | SPFAuthResult::new() |
2558 | .with_domain("test.org") |
2559 | .with_scope(SPFDomainScope::Helo) |
2560 | - .with_result(SPFResult::SoftFail) |
2561 | + .with_result(SpfResult::SoftFail) |
2562 | .with_human_result("dns timed out"), |
2563 | ), |
2564 | ) |
2565 | @@ -481,8 +482,8 @@ mod test { |
2566 | .with_source_ip("a:b:c::e:f".parse().unwrap()) |
2567 | .with_count(99) |
2568 | .with_action_disposition(ActionDisposition::Reject) |
2569 | - .with_dmarc_dkim_result(DMARCResult::Fail) |
2570 | - .with_dmarc_spf_result(DMARCResult::Pass) |
2571 | + .with_dmarc_dkim_result(DmarcResult::Fail) |
2572 | + .with_dmarc_spf_result(DmarcResult::Pass) |
2573 | .with_policy_override_reason( |
2574 | PolicyOverrideReason::new(PolicyOverride::LocalPolicy) |
2575 | .with_comment("on the white list"), |
2576 | @@ -498,14 +499,14 @@ mod test { |
2577 | DKIMAuthResult::new() |
2578 | .with_domain("test2.org") |
2579 | .with_selector("my-other-selector") |
2580 | - .with_result(DKIMResult::Neutral) |
2581 | + .with_result(DkimResult::Neutral) |
2582 | .with_human_result("something went wrong"), |
2583 | ) |
2584 | .with_spf_auth_result( |
2585 | SPFAuthResult::new() |
2586 | .with_domain("test.org") |
2587 | .with_scope(SPFDomainScope::MailFrom) |
2588 | - .with_result(SPFResult::None) |
2589 | + .with_result(SpfResult::None) |
2590 | .with_human_result("no policy found"), |
2591 | ), |
2592 | ); |
2593 | diff --git a/src/report/dmarc/mod.rs b/src/report/dmarc/mod.rs |
2594 | index 658a373..5ba302a 100644 |
2595 | --- a/src/report/dmarc/mod.rs |
2596 | +++ b/src/report/dmarc/mod.rs |
2597 | @@ -15,13 +15,13 @@ use std::fmt::Write; |
2598 | use std::net::IpAddr; |
2599 | |
2600 | use crate::{ |
2601 | - dmarc::DMARC, |
2602 | + dmarc::Dmarc, |
2603 | report::{ |
2604 | - ActionDisposition, Alignment, DKIMAuthResult, DKIMResult, DMARCResult, Disposition, |
2605 | + ActionDisposition, Alignment, DKIMAuthResult, Disposition, DkimResult, DmarcResult, |
2606 | PolicyOverride, PolicyOverrideReason, Record, Report, SPFAuthResult, SPFDomainScope, |
2607 | - SPFResult, |
2608 | + SpfResult, |
2609 | }, |
2610 | - ARCOutput, DKIMOutput, DMARCOutput, SPFOutput, |
2611 | + ArcOutput, DkimOutput, DmarcOutput, SpfOutput, |
2612 | }; |
2613 | |
2614 | impl Report { |
2615 | @@ -182,7 +182,7 @@ impl Report { |
2616 | self |
2617 | } |
2618 | |
2619 | - pub fn with_policy_published(mut self, dmarc: &DMARC) -> Self { |
2620 | + pub fn with_policy_published(mut self, dmarc: &Dmarc) -> Self { |
2621 | self.policy_published.adkim = (&dmarc.adkim).into(); |
2622 | self.policy_published.aspf = (&dmarc.aspf).into(); |
2623 | self.policy_published.p = (&dmarc.p).into(); |
2624 | @@ -206,22 +206,22 @@ impl Record { |
2625 | Record::default() |
2626 | } |
2627 | |
2628 | - pub fn with_dkim_output(mut self, dkim_output: &[DKIMOutput]) { |
2629 | + pub fn with_dkim_output(mut self, dkim_output: &[DkimOutput]) { |
2630 | for dkim in dkim_output { |
2631 | if let Some(signature) = &dkim.signature { |
2632 | let (result, human_result) = match &dkim.result { |
2633 | - crate::DKIMResult::Pass => (DKIMResult::Pass, None), |
2634 | - crate::DKIMResult::Neutral(err) => { |
2635 | - (DKIMResult::Neutral, err.to_string().into()) |
2636 | + crate::DkimResult::Pass => (DkimResult::Pass, None), |
2637 | + crate::DkimResult::Neutral(err) => { |
2638 | + (DkimResult::Neutral, err.to_string().into()) |
2639 | } |
2640 | - crate::DKIMResult::Fail(err) => (DKIMResult::Fail, err.to_string().into()), |
2641 | - crate::DKIMResult::PermError(err) => { |
2642 | - (DKIMResult::PermError, err.to_string().into()) |
2643 | + crate::DkimResult::Fail(err) => (DkimResult::Fail, err.to_string().into()), |
2644 | + crate::DkimResult::PermError(err) => { |
2645 | + (DkimResult::PermError, err.to_string().into()) |
2646 | } |
2647 | - crate::DKIMResult::TempError(err) => { |
2648 | - (DKIMResult::TempError, err.to_string().into()) |
2649 | + crate::DkimResult::TempError(err) => { |
2650 | + (DkimResult::TempError, err.to_string().into()) |
2651 | } |
2652 | - crate::DKIMResult::None => (DKIMResult::None, None), |
2653 | + crate::DkimResult::None => (DkimResult::None, None), |
2654 | }; |
2655 | |
2656 | self.auth_results.dkim.push(DKIMAuthResult { |
2657 | @@ -234,24 +234,24 @@ impl Record { |
2658 | } |
2659 | } |
2660 | |
2661 | - pub fn with_spf_output(mut self, spf_output: &SPFOutput, scope: SPFDomainScope) { |
2662 | + pub fn with_spf_output(mut self, spf_output: &SpfOutput, scope: SPFDomainScope) { |
2663 | self.auth_results.spf.push(SPFAuthResult { |
2664 | domain: spf_output.domain.to_string(), |
2665 | scope, |
2666 | result: match spf_output.result { |
2667 | - crate::SPFResult::Pass => SPFResult::Pass, |
2668 | - crate::SPFResult::Fail => SPFResult::Fail, |
2669 | - crate::SPFResult::SoftFail => SPFResult::SoftFail, |
2670 | - crate::SPFResult::Neutral => SPFResult::Neutral, |
2671 | - crate::SPFResult::TempError => SPFResult::TempError, |
2672 | - crate::SPFResult::PermError => SPFResult::PermError, |
2673 | - crate::SPFResult::None => SPFResult::None, |
2674 | + crate::SpfResult::Pass => SpfResult::Pass, |
2675 | + crate::SpfResult::Fail => SpfResult::Fail, |
2676 | + crate::SpfResult::SoftFail => SpfResult::SoftFail, |
2677 | + crate::SpfResult::Neutral => SpfResult::Neutral, |
2678 | + crate::SpfResult::TempError => SpfResult::TempError, |
2679 | + crate::SpfResult::PermError => SpfResult::PermError, |
2680 | + crate::SpfResult::None => SpfResult::None, |
2681 | }, |
2682 | human_result: None, |
2683 | }); |
2684 | } |
2685 | |
2686 | - pub fn with_dmarc_output(mut self, dmarc_output: &DMARCOutput) { |
2687 | + pub fn with_dmarc_output(mut self, dmarc_output: &DmarcOutput) { |
2688 | self.row.policy_evaluated.disposition = match dmarc_output.policy { |
2689 | crate::dmarc::Policy::None => ActionDisposition::None, |
2690 | crate::dmarc::Policy::Quarantine => ActionDisposition::Quarantine, |
2691 | @@ -262,8 +262,8 @@ impl Record { |
2692 | self.row.policy_evaluated.spf = (&dmarc_output.spf_result).into(); |
2693 | } |
2694 | |
2695 | - pub fn with_arc_output(mut self, arc_output: &ARCOutput) { |
2696 | - if arc_output.result == crate::DKIMResult::Pass { |
2697 | + pub fn with_arc_output(mut self, arc_output: &ArcOutput) { |
2698 | + if arc_output.result == crate::DkimResult::Pass { |
2699 | let mut comment = "arc=pass".to_string(); |
2700 | for set in arc_output.set.iter().rev() { |
2701 | let seal = &set.seal.header; |
2702 | @@ -308,20 +308,20 @@ impl Record { |
2703 | self |
2704 | } |
2705 | |
2706 | - pub fn dmarc_dkim_result(&self) -> DMARCResult { |
2707 | + pub fn dmarc_dkim_result(&self) -> DmarcResult { |
2708 | self.row.policy_evaluated.dkim |
2709 | } |
2710 | |
2711 | - pub fn with_dmarc_dkim_result(mut self, dkim: DMARCResult) -> Self { |
2712 | + pub fn with_dmarc_dkim_result(mut self, dkim: DmarcResult) -> Self { |
2713 | self.row.policy_evaluated.dkim = dkim; |
2714 | self |
2715 | } |
2716 | |
2717 | - pub fn dmarc_spf_result(&self) -> DMARCResult { |
2718 | + pub fn dmarc_spf_result(&self) -> DmarcResult { |
2719 | self.row.policy_evaluated.spf |
2720 | } |
2721 | |
2722 | - pub fn with_dmarc_spf_result(mut self, spf: DMARCResult) -> Self { |
2723 | + pub fn with_dmarc_spf_result(mut self, spf: DmarcResult) -> Self { |
2724 | self.row.policy_evaluated.spf = spf; |
2725 | self |
2726 | } |
2727 | @@ -404,11 +404,11 @@ impl DKIMAuthResult { |
2728 | self |
2729 | } |
2730 | |
2731 | - pub fn result(&self) -> DKIMResult { |
2732 | + pub fn result(&self) -> DkimResult { |
2733 | self.result |
2734 | } |
2735 | |
2736 | - pub fn with_result(mut self, result: DKIMResult) -> Self { |
2737 | + pub fn with_result(mut self, result: DkimResult) -> Self { |
2738 | self.result = result; |
2739 | self |
2740 | } |
2741 | @@ -446,11 +446,11 @@ impl SPFAuthResult { |
2742 | self |
2743 | } |
2744 | |
2745 | - pub fn result(&self) -> SPFResult { |
2746 | + pub fn result(&self) -> SpfResult { |
2747 | self.result |
2748 | } |
2749 | |
2750 | - pub fn with_result(mut self, result: SPFResult) -> Self { |
2751 | + pub fn with_result(mut self, result: SpfResult) -> Self { |
2752 | self.result = result; |
2753 | self |
2754 | } |
2755 | @@ -487,11 +487,11 @@ impl PolicyOverrideReason { |
2756 | } |
2757 | } |
2758 | |
2759 | - impl From<&crate::DMARCResult> for DMARCResult { |
2760 | - fn from(result: &crate::DMARCResult) -> Self { |
2761 | + impl From<&crate::DmarcResult> for DmarcResult { |
2762 | + fn from(result: &crate::DmarcResult) -> Self { |
2763 | match result { |
2764 | - crate::DMARCResult::Pass => DMARCResult::Pass, |
2765 | - _ => DMARCResult::Fail, |
2766 | + crate::DmarcResult::Pass => DmarcResult::Pass, |
2767 | + _ => DmarcResult::Fail, |
2768 | } |
2769 | } |
2770 | } |
2771 | diff --git a/src/report/dmarc/parse.rs b/src/report/dmarc/parse.rs |
2772 | index 64ab859..a30eefb 100644 |
2773 | --- a/src/report/dmarc/parse.rs |
2774 | +++ b/src/report/dmarc/parse.rs |
2775 | @@ -17,10 +17,10 @@ use quick_xml::events::{BytesStart, Event}; |
2776 | use quick_xml::reader::Reader; |
2777 | |
2778 | use crate::report::{ |
2779 | - ActionDisposition, Alignment, AuthResult, DKIMAuthResult, DKIMResult, DMARCResult, DateRange, |
2780 | - Disposition, Error, Extension, Identifier, PolicyEvaluated, PolicyOverride, |
2781 | + ActionDisposition, Alignment, AuthResult, DKIMAuthResult, DateRange, Disposition, DkimResult, |
2782 | + DmarcResult, Error, Extension, Identifier, PolicyEvaluated, PolicyOverride, |
2783 | PolicyOverrideReason, PolicyPublished, Record, Report, ReportMetadata, Row, SPFAuthResult, |
2784 | - SPFDomainScope, SPFResult, |
2785 | + SPFDomainScope, SpfResult, |
2786 | }; |
2787 | |
2788 | impl Report { |
2789 | @@ -589,48 +589,48 @@ impl FromStr for PolicyOverride { |
2790 | } |
2791 | } |
2792 | |
2793 | - impl FromStr for DMARCResult { |
2794 | + impl FromStr for DmarcResult { |
2795 | type Err = (); |
2796 | |
2797 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
2798 | Ok(match s.as_bytes() { |
2799 | - b"pass" => DMARCResult::Pass, |
2800 | - b"fail" => DMARCResult::Fail, |
2801 | - _ => DMARCResult::Unspecified, |
2802 | + b"pass" => DmarcResult::Pass, |
2803 | + b"fail" => DmarcResult::Fail, |
2804 | + _ => DmarcResult::Unspecified, |
2805 | }) |
2806 | } |
2807 | } |
2808 | |
2809 | - impl FromStr for DKIMResult { |
2810 | + impl FromStr for DkimResult { |
2811 | type Err = (); |
2812 | |
2813 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
2814 | Ok(match s.as_bytes() { |
2815 | - b"none" => DKIMResult::None, |
2816 | - b"pass" => DKIMResult::Pass, |
2817 | - b"fail" => DKIMResult::Fail, |
2818 | - b"policy" => DKIMResult::Policy, |
2819 | - b"neutral" => DKIMResult::Neutral, |
2820 | - b"temperror" => DKIMResult::TempError, |
2821 | - b"permerror" => DKIMResult::PermError, |
2822 | - _ => DKIMResult::None, |
2823 | + b"none" => DkimResult::None, |
2824 | + b"pass" => DkimResult::Pass, |
2825 | + b"fail" => DkimResult::Fail, |
2826 | + b"policy" => DkimResult::Policy, |
2827 | + b"neutral" => DkimResult::Neutral, |
2828 | + b"temperror" => DkimResult::TempError, |
2829 | + b"permerror" => DkimResult::PermError, |
2830 | + _ => DkimResult::None, |
2831 | }) |
2832 | } |
2833 | } |
2834 | |
2835 | - impl FromStr for SPFResult { |
2836 | + impl FromStr for SpfResult { |
2837 | type Err = (); |
2838 | |
2839 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
2840 | Ok(match s.as_bytes() { |
2841 | - b"none" => SPFResult::None, |
2842 | - b"pass" => SPFResult::Pass, |
2843 | - b"fail" => SPFResult::Fail, |
2844 | - b"softfail" => SPFResult::SoftFail, |
2845 | - b"neutral" => SPFResult::Neutral, |
2846 | - b"temperror" => SPFResult::TempError, |
2847 | - b"permerror" => SPFResult::PermError, |
2848 | - _ => SPFResult::None, |
2849 | + b"none" => SpfResult::None, |
2850 | + b"pass" => SpfResult::Pass, |
2851 | + b"fail" => SpfResult::Fail, |
2852 | + b"softfail" => SpfResult::SoftFail, |
2853 | + b"neutral" => SpfResult::Neutral, |
2854 | + b"temperror" => SpfResult::TempError, |
2855 | + b"permerror" => SpfResult::PermError, |
2856 | + _ => SpfResult::None, |
2857 | }) |
2858 | } |
2859 | } |
2860 | diff --git a/src/report/mod.rs b/src/report/mod.rs |
2861 | index e3bd93a..242e8cf 100644 |
2862 | --- a/src/report/mod.rs |
2863 | +++ b/src/report/mod.rs |
2864 | @@ -73,7 +73,7 @@ pub struct PolicyPublished { |
2865 | impl Eq for PolicyPublished {} |
2866 | |
2867 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
2868 | - pub enum DMARCResult { |
2869 | + pub enum DmarcResult { |
2870 | Pass, |
2871 | Fail, |
2872 | Unspecified, |
2873 | @@ -98,8 +98,8 @@ pub struct PolicyOverrideReason { |
2874 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] |
2875 | pub struct PolicyEvaluated { |
2876 | disposition: ActionDisposition, |
2877 | - dkim: DMARCResult, |
2878 | - spf: DMARCResult, |
2879 | + dkim: DmarcResult, |
2880 | + spf: DmarcResult, |
2881 | reason: Vec<PolicyOverrideReason>, |
2882 | } |
2883 | |
2884 | @@ -124,7 +124,7 @@ pub struct Identifier { |
2885 | } |
2886 | |
2887 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
2888 | - pub enum DKIMResult { |
2889 | + pub enum DkimResult { |
2890 | None, |
2891 | Pass, |
2892 | Fail, |
2893 | @@ -138,7 +138,7 @@ pub enum DKIMResult { |
2894 | pub struct DKIMAuthResult { |
2895 | domain: String, |
2896 | selector: String, |
2897 | - result: DKIMResult, |
2898 | + result: DkimResult, |
2899 | human_result: Option<String>, |
2900 | } |
2901 | |
2902 | @@ -150,7 +150,7 @@ pub enum SPFDomainScope { |
2903 | } |
2904 | |
2905 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
2906 | - pub enum SPFResult { |
2907 | + pub enum SpfResult { |
2908 | None, |
2909 | Neutral, |
2910 | Pass, |
2911 | @@ -164,7 +164,7 @@ pub enum SPFResult { |
2912 | pub struct SPFAuthResult { |
2913 | domain: String, |
2914 | scope: SPFDomainScope, |
2915 | - result: SPFResult, |
2916 | + result: SpfResult, |
2917 | human_result: Option<String>, |
2918 | } |
2919 | |
2920 | @@ -221,9 +221,9 @@ impl Default for ActionDisposition { |
2921 | } |
2922 | } |
2923 | |
2924 | - impl Default for DMARCResult { |
2925 | + impl Default for DmarcResult { |
2926 | fn default() -> Self { |
2927 | - DMARCResult::Unspecified |
2928 | + DmarcResult::Unspecified |
2929 | } |
2930 | } |
2931 | |
2932 | @@ -233,15 +233,15 @@ impl Default for PolicyOverride { |
2933 | } |
2934 | } |
2935 | |
2936 | - impl Default for DKIMResult { |
2937 | + impl Default for DkimResult { |
2938 | fn default() -> Self { |
2939 | - DKIMResult::None |
2940 | + DkimResult::None |
2941 | } |
2942 | } |
2943 | |
2944 | - impl Default for SPFResult { |
2945 | + impl Default for SpfResult { |
2946 | fn default() -> Self { |
2947 | - SPFResult::None |
2948 | + SpfResult::None |
2949 | } |
2950 | } |
2951 | |
2952 | diff --git a/src/spf/mod.rs b/src/spf/mod.rs |
2953 | index 799b975..ea912b1 100644 |
2954 | --- a/src/spf/mod.rs |
2955 | +++ b/src/spf/mod.rs |
2956 | @@ -17,7 +17,7 @@ use std::{ |
2957 | net::{Ipv4Addr, Ipv6Addr}, |
2958 | }; |
2959 | |
2960 | - use crate::{is_within_pct, SPFOutput, SPFResult, Version}; |
2961 | + use crate::{is_within_pct, SpfOutput, SpfResult, Version}; |
2962 | |
2963 | /* |
2964 | "+" pass |
2965 | @@ -131,7 +131,7 @@ pub enum Macro { |
2966 | } |
2967 | |
2968 | #[derive(Debug, PartialEq, Eq, Clone)] |
2969 | - pub struct SPF { |
2970 | + pub struct Spf { |
2971 | version: Version, |
2972 | directives: Vec<Directive>, |
2973 | exp: Option<Macro>, |
2974 | @@ -170,31 +170,31 @@ impl Mechanism { |
2975 | } |
2976 | } |
2977 | |
2978 | - impl TryFrom<&str> for SPFResult { |
2979 | + impl TryFrom<&str> for SpfResult { |
2980 | type Error = (); |
2981 | |
2982 | fn try_from(value: &str) -> Result<Self, Self::Error> { |
2983 | if value.eq_ignore_ascii_case("pass") { |
2984 | - Ok(SPFResult::Pass) |
2985 | + Ok(SpfResult::Pass) |
2986 | } else if value.eq_ignore_ascii_case("fail") { |
2987 | - Ok(SPFResult::Fail) |
2988 | + Ok(SpfResult::Fail) |
2989 | } else if value.eq_ignore_ascii_case("softfail") { |
2990 | - Ok(SPFResult::SoftFail) |
2991 | + Ok(SpfResult::SoftFail) |
2992 | } else if value.eq_ignore_ascii_case("neutral") { |
2993 | - Ok(SPFResult::Neutral) |
2994 | + Ok(SpfResult::Neutral) |
2995 | } else if value.eq_ignore_ascii_case("temperror") { |
2996 | - Ok(SPFResult::TempError) |
2997 | + Ok(SpfResult::TempError) |
2998 | } else if value.eq_ignore_ascii_case("permerror") { |
2999 | - Ok(SPFResult::PermError) |
3000 | + Ok(SpfResult::PermError) |
3001 | } else if value.eq_ignore_ascii_case("none") { |
3002 | - Ok(SPFResult::None) |
3003 | + Ok(SpfResult::None) |
3004 | } else { |
3005 | Err(()) |
3006 | } |
3007 | } |
3008 | } |
3009 | |
3010 | - impl TryFrom<String> for SPFResult { |
3011 | + impl TryFrom<String> for SpfResult { |
3012 | type Error = (); |
3013 | |
3014 | fn try_from(value: String) -> Result<Self, Self::Error> { |
3015 | @@ -202,32 +202,32 @@ impl TryFrom<String> for SPFResult { |
3016 | } |
3017 | } |
3018 | |
3019 | - impl SPFOutput { |
3020 | + impl SpfOutput { |
3021 | pub(crate) fn new(domain: String) -> Self { |
3022 | - SPFOutput { |
3023 | - result: SPFResult::None, |
3024 | + SpfOutput { |
3025 | + result: SpfResult::None, |
3026 | report: None, |
3027 | explanation: None, |
3028 | domain, |
3029 | } |
3030 | } |
3031 | |
3032 | - pub(crate) fn with_result(mut self, result: SPFResult) -> Self { |
3033 | + pub(crate) fn with_result(mut self, result: SpfResult) -> Self { |
3034 | self.result = result; |
3035 | self |
3036 | } |
3037 | |
3038 | - pub(crate) fn with_report(mut self, spf: &SPF) -> Self { |
3039 | + pub(crate) fn with_report(mut self, spf: &Spf) -> Self { |
3040 | match &spf.ra { |
3041 | Some(ra) if is_within_pct(spf.rp) => { |
3042 | if match self.result { |
3043 | - SPFResult::Fail => (spf.rr & RR_FAIL) != 0, |
3044 | - SPFResult::SoftFail => (spf.rr & RR_SOFTFAIL) != 0, |
3045 | - SPFResult::Neutral | SPFResult::None => (spf.rr & RR_NEUTRAL_NONE) != 0, |
3046 | - SPFResult::TempError | SPFResult::PermError => { |
3047 | + SpfResult::Fail => (spf.rr & RR_FAIL) != 0, |
3048 | + SpfResult::SoftFail => (spf.rr & RR_SOFTFAIL) != 0, |
3049 | + SpfResult::Neutral | SpfResult::None => (spf.rr & RR_NEUTRAL_NONE) != 0, |
3050 | + SpfResult::TempError | SpfResult::PermError => { |
3051 | (spf.rr & RR_TEMP_PERM_ERROR) != 0 |
3052 | } |
3053 | - SPFResult::Pass => false, |
3054 | + SpfResult::Pass => false, |
3055 | } { |
3056 | self.report = format!("{}@{}", String::from_utf8_lossy(ra), self.domain).into(); |
3057 | } |
3058 | @@ -242,7 +242,7 @@ impl SPFOutput { |
3059 | self |
3060 | } |
3061 | |
3062 | - pub fn result(&self) -> SPFResult { |
3063 | + pub fn result(&self) -> SpfResult { |
3064 | self.result |
3065 | } |
3066 | |
3067 | diff --git a/src/spf/parse.rs b/src/spf/parse.rs |
3068 | index f864351..46912d3 100644 |
3069 | --- a/src/spf/parse.rs |
3070 | +++ b/src/spf/parse.rs |
3071 | @@ -19,12 +19,12 @@ use crate::{ |
3072 | }; |
3073 | |
3074 | use super::{ |
3075 | - Directive, Macro, Mechanism, Qualifier, Variable, RR_FAIL, RR_NEUTRAL_NONE, RR_SOFTFAIL, |
3076 | - RR_TEMP_PERM_ERROR, SPF, |
3077 | + Directive, Macro, Mechanism, Qualifier, Spf, Variable, RR_FAIL, RR_NEUTRAL_NONE, RR_SOFTFAIL, |
3078 | + RR_TEMP_PERM_ERROR, |
3079 | }; |
3080 | |
3081 | - impl TxtRecordParser for SPF { |
3082 | - fn parse(bytes: &[u8]) -> crate::Result<SPF> { |
3083 | + impl TxtRecordParser for Spf { |
3084 | + fn parse(bytes: &[u8]) -> crate::Result<Spf> { |
3085 | let mut record = bytes.iter(); |
3086 | if !matches!(record.key(), Some(k) if k == V) |
3087 | || !record.match_bytes(b"spf1") |
3088 | @@ -33,7 +33,7 @@ impl TxtRecordParser for SPF { |
3089 | return Err(Error::InvalidRecordType); |
3090 | } |
3091 | |
3092 | - let mut spf = SPF { |
3093 | + let mut spf = Spf { |
3094 | version: Version::V1, |
3095 | directives: Vec::new(), |
3096 | redirect: None, |
3097 | @@ -738,8 +738,8 @@ mod test { |
3098 | use crate::{ |
3099 | common::parse::TxtRecordParser, |
3100 | spf::{ |
3101 | - Directive, Macro, Mechanism, Qualifier, Variable, Version, RR_FAIL, RR_NEUTRAL_NONE, |
3102 | - RR_SOFTFAIL, RR_TEMP_PERM_ERROR, SPF, |
3103 | + Directive, Macro, Mechanism, Qualifier, Spf, Variable, Version, RR_FAIL, |
3104 | + RR_NEUTRAL_NONE, RR_SOFTFAIL, RR_TEMP_PERM_ERROR, |
3105 | }, |
3106 | }; |
3107 | |
3108 | @@ -750,7 +750,7 @@ mod test { |
3109 | for (record, expected_result) in [ |
3110 | ( |
3111 | "v=spf1 +mx a:colo.example.com/28 -all", |
3112 | - SPF { |
3113 | + Spf { |
3114 | version: Version::V1, |
3115 | ra: None, |
3116 | rp: 100, |
3117 | @@ -780,7 +780,7 @@ mod test { |
3118 | ), |
3119 | ( |
3120 | "v=spf1 a:A.EXAMPLE.COM -all", |
3121 | - SPF { |
3122 | + Spf { |
3123 | version: Version::V1, |
3124 | ra: None, |
3125 | rp: 100, |
3126 | @@ -802,7 +802,7 @@ mod test { |
3127 | ), |
3128 | ( |
3129 | "v=spf1 +mx -all", |
3130 | - SPF { |
3131 | + Spf { |
3132 | version: Version::V1, |
3133 | ra: None, |
3134 | rp: 100, |
3135 | @@ -824,7 +824,7 @@ mod test { |
3136 | ), |
3137 | ( |
3138 | "v=spf1 +mx redirect=_spf.example.com", |
3139 | - SPF { |
3140 | + Spf { |
3141 | version: Version::V1, |
3142 | ra: None, |
3143 | rp: 100, |
3144 | @@ -843,7 +843,7 @@ mod test { |
3145 | ), |
3146 | ( |
3147 | "v=spf1 a mx -all", |
3148 | - SPF { |
3149 | + Spf { |
3150 | version: Version::V1, |
3151 | ra: None, |
3152 | rp: 100, |
3153 | @@ -873,7 +873,7 @@ mod test { |
3154 | ), |
3155 | ( |
3156 | "v=spf1 include:example.com include:example.org -all", |
3157 | - SPF { |
3158 | + Spf { |
3159 | version: Version::V1, |
3160 | ra: None, |
3161 | rp: 100, |
3162 | @@ -899,7 +899,7 @@ mod test { |
3163 | ), |
3164 | ( |
3165 | "v=spf1 exists:%{ir}.%{l1r+-}._spf.%{d} -all", |
3166 | - SPF { |
3167 | + Spf { |
3168 | version: Version::V1, |
3169 | ra: None, |
3170 | rp: 100, |
3171 | @@ -943,7 +943,7 @@ mod test { |
3172 | ), |
3173 | ( |
3174 | "v=spf1 mx -all exp=explain._spf.%{d}", |
3175 | - SPF { |
3176 | + Spf { |
3177 | version: Version::V1, |
3178 | ra: None, |
3179 | rp: 100, |
3180 | @@ -975,7 +975,7 @@ mod test { |
3181 | ), |
3182 | ( |
3183 | "v=spf1 ip4:192.0.2.1 ip4:192.0.2.129 -all", |
3184 | - SPF { |
3185 | + Spf { |
3186 | version: Version::V1, |
3187 | ra: None, |
3188 | rp: 100, |
3189 | @@ -1003,7 +1003,7 @@ mod test { |
3190 | ), |
3191 | ( |
3192 | "v=spf1 ip4:192.0.2.0/24 mx -all", |
3193 | - SPF { |
3194 | + Spf { |
3195 | version: Version::V1, |
3196 | ra: None, |
3197 | rp: 100, |
3198 | @@ -1032,7 +1032,7 @@ mod test { |
3199 | ), |
3200 | ( |
3201 | "v=spf1 mx/30 mx:example.org/30 -all", |
3202 | - SPF { |
3203 | + Spf { |
3204 | version: Version::V1, |
3205 | ra: None, |
3206 | rp: 100, |
3207 | @@ -1062,7 +1062,7 @@ mod test { |
3208 | ), |
3209 | ( |
3210 | "v=spf1 ptr -all", |
3211 | - SPF { |
3212 | + Spf { |
3213 | version: Version::V1, |
3214 | ra: None, |
3215 | rp: 100, |
3216 | @@ -1082,7 +1082,7 @@ mod test { |
3217 | ), |
3218 | ( |
3219 | "v=spf1 exists:%{l1r+}.%{d}", |
3220 | - SPF { |
3221 | + Spf { |
3222 | version: Version::V1, |
3223 | ra: None, |
3224 | rp: 100, |
3225 | @@ -1115,7 +1115,7 @@ mod test { |
3226 | ), |
3227 | ( |
3228 | "v=spf1 exists:%{ir}.%{l1r+}.%{d}", |
3229 | - SPF { |
3230 | + Spf { |
3231 | version: Version::V1, |
3232 | ra: None, |
3233 | rp: 100, |
3234 | @@ -1156,7 +1156,7 @@ mod test { |
3235 | ), |
3236 | ( |
3237 | "v=spf1 exists:_h.%{h}._l.%{l}._o.%{o}._i.%{i}._spf.%{d} ?all", |
3238 | - SPF { |
3239 | + Spf { |
3240 | version: Version::V1, |
3241 | ra: None, |
3242 | rp: 100, |
3243 | @@ -1217,7 +1217,7 @@ mod test { |
3244 | ), |
3245 | ( |
3246 | "v=spf1 mx ?exists:%{ir}.whitelist.example.org -all", |
3247 | - SPF { |
3248 | + Spf { |
3249 | version: Version::V1, |
3250 | ra: None, |
3251 | rp: 100, |
3252 | @@ -1254,7 +1254,7 @@ mod test { |
3253 | ), |
3254 | ( |
3255 | "v=spf1 mx exists:%{l}._%-spf_%_verify%%.%{d} -all", |
3256 | - SPF { |
3257 | + Spf { |
3258 | version: Version::V1, |
3259 | ra: None, |
3260 | rp: 100, |
3261 | @@ -1298,7 +1298,7 @@ mod test { |
3262 | ), |
3263 | ( |
3264 | "v=spf1 mx redirect=%{l1r+}._at_.%{o,=_/}._spf.%{d}", |
3265 | - SPF { |
3266 | + Spf { |
3267 | version: Version::V1, |
3268 | ra: None, |
3269 | rp: 100, |
3270 | @@ -1345,7 +1345,7 @@ mod test { |
3271 | ), |
3272 | ( |
3273 | "v=spf1 -ip4:192.0.2.0/24 a//96 +all", |
3274 | - SPF { |
3275 | + Spf { |
3276 | version: Version::V1, |
3277 | ra: None, |
3278 | rp: 100, |
3279 | @@ -1377,7 +1377,7 @@ mod test { |
3280 | "v=spf1 +mx/11//100 ~a:domain.com/12/123 ?ip6:::1 ", |
3281 | "-ip6:a::b/111 ip6:1080::8:800:68.0.3.1/96 " |
3282 | ), |
3283 | - SPF { |
3284 | + Spf { |
3285 | version: Version::V1, |
3286 | ra: None, |
3287 | rp: 100, |
3288 | @@ -1427,7 +1427,7 @@ mod test { |
3289 | ), |
3290 | ( |
3291 | concat!("v=spf1 mx:example.org -all ra=postmaster rp=15 rr=e:f:s:n"), |
3292 | - SPF { |
3293 | + Spf { |
3294 | version: Version::V1, |
3295 | ra: b"postmaster".to_vec().into(), |
3296 | rp: 15, |
3297 | @@ -1449,7 +1449,7 @@ mod test { |
3298 | ), |
3299 | ] { |
3300 | assert_eq!( |
3301 | - SPF::parse(record.as_bytes()) |
3302 | + Spf::parse(record.as_bytes()) |
3303 | .unwrap_or_else(|err| panic!("{:?} : {:?}", record, err)), |
3304 | expected_result, |
3305 | "{}", |
3306 | diff --git a/src/spf/verify.rs b/src/spf/verify.rs |
3307 | index cd1b697..e85dd4c 100644 |
3308 | --- a/src/spf/verify.rs |
3309 | +++ b/src/spf/verify.rs |
3310 | @@ -13,12 +13,13 @@ use std::{ |
3311 | time::Instant, |
3312 | }; |
3313 | |
3314 | - use crate::{Error, Policy, Resolver, SPFOutput, SPFResult}; |
3315 | + use crate::{Error, Policy, Resolver, SpfOutput, SpfResult}; |
3316 | |
3317 | - use super::{Macro, Mechanism, Qualifier, Variables, SPF}; |
3318 | + use super::{Macro, Mechanism, Qualifier, Spf, Variables}; |
3319 | |
3320 | impl Resolver { |
3321 | - pub async fn verify_spf_helo(&self, ip: IpAddr, helo_domain: &str) -> SPFOutput { |
3322 | + /// Verifies the SPF EHLO identity |
3323 | + pub async fn verify_spf_helo(&self, ip: IpAddr, helo_domain: &str) -> SpfOutput { |
3324 | if helo_domain.has_labels() { |
3325 | self.check_host( |
3326 | ip, |
3327 | @@ -28,16 +29,17 @@ impl Resolver { |
3328 | ) |
3329 | .await |
3330 | } else { |
3331 | - SPFOutput::new(helo_domain.to_string()).with_result(SPFResult::None) |
3332 | + SpfOutput::new(helo_domain.to_string()).with_result(SpfResult::None) |
3333 | } |
3334 | } |
3335 | |
3336 | + /// Verifies the SPF MAIL FROM identity |
3337 | pub async fn verify_spf_sender( |
3338 | &self, |
3339 | ip: IpAddr, |
3340 | helo_domain: &str, |
3341 | sender: &str, |
3342 | - ) -> SPFOutput { |
3343 | + ) -> SpfOutput { |
3344 | self.check_host( |
3345 | ip, |
3346 | sender.rsplit_once('@').map_or(helo_domain, |(_, d)| d), |
3347 | @@ -47,12 +49,13 @@ impl Resolver { |
3348 | .await |
3349 | } |
3350 | |
3351 | - pub async fn verify_spf(&self, ip: IpAddr, helo_domain: &str, mail_from: &str) -> SPFOutput { |
3352 | + /// Verifies both the SPF EHLO and MAIL FROM identities |
3353 | + pub async fn verify_spf(&self, ip: IpAddr, helo_domain: &str, mail_from: &str) -> SpfOutput { |
3354 | // Verify HELO identity |
3355 | let output = self.verify_spf_helo(ip, helo_domain).await; |
3356 | match output.result() { |
3357 | - SPFResult::TempError | SPFResult::Pass => (), |
3358 | - SPFResult::None | SPFResult::PermError if self.verify_policy != Policy::VeryStrict => {} |
3359 | + SpfResult::TempError | SpfResult::Pass => (), |
3360 | + SpfResult::None | SpfResult::PermError if self.verify_policy != Policy::VeryStrict => {} |
3361 | _ => return output, |
3362 | } |
3363 | |
3364 | @@ -67,10 +70,10 @@ impl Resolver { |
3365 | domain: &str, |
3366 | helo_domain: &str, |
3367 | sender: &str, |
3368 | - ) -> SPFOutput { |
3369 | - let output = SPFOutput::new(domain.to_string()); |
3370 | + ) -> SpfOutput { |
3371 | + let output = SpfOutput::new(domain.to_string()); |
3372 | if domain.is_empty() || domain.len() > 63 || !domain.has_labels() { |
3373 | - return output.with_result(SPFResult::None); |
3374 | + return output.with_result(SpfResult::None); |
3375 | } |
3376 | let mut vars = Variables::new(); |
3377 | let mut has_p_var = false; |
3378 | @@ -85,7 +88,7 @@ impl Resolver { |
3379 | vars.set_helo_domain(helo_domain.as_bytes()); |
3380 | |
3381 | let mut lookup_limit = LookupLimit::new(); |
3382 | - let mut spf_record = match self.txt_lookup::<SPF>(domain).await { |
3383 | + let mut spf_record = match self.txt_lookup::<Spf>(domain).await { |
3384 | Ok(spf_record) => spf_record, |
3385 | Err(err) => return output.with_result(err.into()), |
3386 | }; |
3387 | @@ -101,7 +104,7 @@ impl Resolver { |
3388 | if !has_p_var && directive.mechanism.needs_ptr() { |
3389 | if !lookup_limit.can_lookup() { |
3390 | return output |
3391 | - .with_result(SPFResult::PermError) |
3392 | + .with_result(SpfResult::PermError) |
3393 | .with_report(&spf_record); |
3394 | } |
3395 | if let Some(ptr) = self |
3396 | @@ -126,7 +129,7 @@ impl Resolver { |
3397 | } => { |
3398 | if !lookup_limit.can_lookup() { |
3399 | return output |
3400 | - .with_result(SPFResult::PermError) |
3401 | + .with_result(SpfResult::PermError) |
3402 | .with_report(&spf_record); |
3403 | } |
3404 | match self |
3405 | @@ -142,7 +145,7 @@ impl Resolver { |
3406 | Ok(false) | Err(Error::DNSRecordNotFound(_)) => false, |
3407 | Err(_) => { |
3408 | return output |
3409 | - .with_result(SPFResult::TempError) |
3410 | + .with_result(SpfResult::TempError) |
3411 | .with_report(&spf_record); |
3412 | } |
3413 | } |
3414 | @@ -154,7 +157,7 @@ impl Resolver { |
3415 | } => { |
3416 | if !lookup_limit.can_lookup() { |
3417 | return output |
3418 | - .with_result(SPFResult::PermError) |
3419 | + .with_result(SpfResult::PermError) |
3420 | .with_report(&spf_record); |
3421 | } |
3422 | |
3423 | @@ -167,7 +170,7 @@ impl Resolver { |
3424 | for record in records.iter() { |
3425 | if !lookup_limit.can_lookup() { |
3426 | return output |
3427 | - .with_result(SPFResult::PermError) |
3428 | + .with_result(SpfResult::PermError) |
3429 | .with_report(&spf_record); |
3430 | } |
3431 | |
3432 | @@ -182,7 +185,7 @@ impl Resolver { |
3433 | Ok(false) | Err(Error::DNSRecordNotFound(_)) => (), |
3434 | Err(_) => { |
3435 | return output |
3436 | - .with_result(SPFResult::TempError) |
3437 | + .with_result(SpfResult::TempError) |
3438 | .with_report(&spf_record); |
3439 | } |
3440 | } |
3441 | @@ -191,7 +194,7 @@ impl Resolver { |
3442 | Err(Error::DNSRecordNotFound(_)) => (), |
3443 | Err(_) => { |
3444 | return output |
3445 | - .with_result(SPFResult::TempError) |
3446 | + .with_result(SpfResult::TempError) |
3447 | .with_report(&spf_record); |
3448 | } |
3449 | } |
3450 | @@ -200,12 +203,12 @@ impl Resolver { |
3451 | Mechanism::Include { macro_string } => { |
3452 | if !lookup_limit.can_lookup() { |
3453 | return output |
3454 | - .with_result(SPFResult::PermError) |
3455 | + .with_result(SpfResult::PermError) |
3456 | .with_report(&spf_record); |
3457 | } |
3458 | |
3459 | let target_name = macro_string.eval(&vars, &domain, true); |
3460 | - match self.txt_lookup::<SPF>(target_name.as_ref()).await { |
3461 | + match self.txt_lookup::<Spf>(target_name.as_ref()).await { |
3462 | Ok(included_spf) => { |
3463 | let new_domain = target_name.to_string(); |
3464 | include_stack.push(( |
3465 | @@ -224,12 +227,12 @@ impl Resolver { |
3466 | | Error::ParseError, |
3467 | ) => { |
3468 | return output |
3469 | - .with_result(SPFResult::PermError) |
3470 | + .with_result(SpfResult::PermError) |
3471 | .with_report(&spf_record) |
3472 | } |
3473 | Err(_) => { |
3474 | return output |
3475 | - .with_result(SPFResult::TempError) |
3476 | + .with_result(SpfResult::TempError) |
3477 | .with_report(&spf_record) |
3478 | } |
3479 | } |
3480 | @@ -237,7 +240,7 @@ impl Resolver { |
3481 | Mechanism::Ptr { macro_string } => { |
3482 | if !lookup_limit.can_lookup() { |
3483 | return output |
3484 | - .with_result(SPFResult::PermError) |
3485 | + .with_result(SpfResult::PermError) |
3486 | .with_report(&spf_record); |
3487 | } |
3488 | |
3489 | @@ -265,7 +268,7 @@ impl Resolver { |
3490 | Mechanism::Exists { macro_string } => { |
3491 | if !lookup_limit.can_lookup() { |
3492 | return output |
3493 | - .with_result(SPFResult::PermError) |
3494 | + .with_result(SpfResult::PermError) |
3495 | .with_report(&spf_record); |
3496 | } |
3497 | |
3498 | @@ -276,7 +279,7 @@ impl Resolver { |
3499 | result |
3500 | } else { |
3501 | return output |
3502 | - .with_result(SPFResult::TempError) |
3503 | + .with_result(SpfResult::TempError) |
3504 | .with_report(&spf_record); |
3505 | } |
3506 | } |
3507 | @@ -293,7 +296,7 @@ impl Resolver { |
3508 | directives = spf_record.directives.iter().enumerate().skip(prev_pos); |
3509 | let (_, directive) = directives.next().unwrap(); |
3510 | |
3511 | - if matches!(result, Some(SPFResult::Pass)) { |
3512 | + if matches!(result, Some(SpfResult::Pass)) { |
3513 | result = Some((&directive.qualifier).into()); |
3514 | break; |
3515 | } else { |
3516 | @@ -306,12 +309,12 @@ impl Resolver { |
3517 | if let (Some(macro_string), None) = (&spf_record.redirect, &result) { |
3518 | if !lookup_limit.can_lookup() { |
3519 | return output |
3520 | - .with_result(SPFResult::PermError) |
3521 | + .with_result(SpfResult::PermError) |
3522 | .with_report(&spf_record); |
3523 | } |
3524 | |
3525 | let target_name = macro_string.eval(&vars, &domain, true); |
3526 | - match self.txt_lookup::<SPF>(target_name.as_ref()).await { |
3527 | + match self.txt_lookup::<Spf>(target_name.as_ref()).await { |
3528 | Ok(redirect_spf) => { |
3529 | let new_domain = target_name.to_string(); |
3530 | spf_record = redirect_spf; |
3531 | @@ -326,12 +329,12 @@ impl Resolver { |
3532 | | Error::ParseError, |
3533 | ) => { |
3534 | return output |
3535 | - .with_result(SPFResult::PermError) |
3536 | + .with_result(SpfResult::PermError) |
3537 | .with_report(&spf_record) |
3538 | } |
3539 | Err(_) => { |
3540 | return output |
3541 | - .with_result(SPFResult::TempError) |
3542 | + .with_result(SpfResult::TempError) |
3543 | .with_report(&spf_record) |
3544 | } |
3545 | } |
3546 | @@ -342,20 +345,20 @@ impl Resolver { |
3547 | } |
3548 | |
3549 | // Evaluate explain |
3550 | - if let (Some(macro_string), Some(SPFResult::Fail { .. })) = (&spf_record.exp, &result) { |
3551 | + if let (Some(macro_string), Some(SpfResult::Fail { .. })) = (&spf_record.exp, &result) { |
3552 | if let Ok(macro_string) = self |
3553 | .txt_lookup::<Macro>(macro_string.eval(&vars, &domain, true).to_string()) |
3554 | .await |
3555 | { |
3556 | return output |
3557 | - .with_result(SPFResult::Fail) |
3558 | + .with_result(SpfResult::Fail) |
3559 | .with_explanation(macro_string.eval(&vars, &domain, false).to_string()) |
3560 | .with_report(&spf_record); |
3561 | } |
3562 | } |
3563 | |
3564 | output |
3565 | - .with_result(result.unwrap_or(SPFResult::Neutral)) |
3566 | + .with_result(result.unwrap_or(SpfResult::Neutral)) |
3567 | .with_report(&spf_record) |
3568 | } |
3569 | |
3570 | @@ -430,23 +433,23 @@ impl IpMask for Ipv4Addr { |
3571 | } |
3572 | } |
3573 | |
3574 | - impl From<&Qualifier> for SPFResult { |
3575 | + impl From<&Qualifier> for SpfResult { |
3576 | fn from(q: &Qualifier) -> Self { |
3577 | match q { |
3578 | - Qualifier::Pass => SPFResult::Pass, |
3579 | - Qualifier::Fail => SPFResult::Fail, |
3580 | - Qualifier::SoftFail => SPFResult::SoftFail, |
3581 | - Qualifier::Neutral => SPFResult::Neutral, |
3582 | + Qualifier::Pass => SpfResult::Pass, |
3583 | + Qualifier::Fail => SpfResult::Fail, |
3584 | + Qualifier::SoftFail => SpfResult::SoftFail, |
3585 | + Qualifier::Neutral => SpfResult::Neutral, |
3586 | } |
3587 | } |
3588 | } |
3589 | |
3590 | - impl From<Error> for SPFResult { |
3591 | + impl From<Error> for SpfResult { |
3592 | fn from(err: Error) -> Self { |
3593 | match err { |
3594 | - Error::DNSRecordNotFound(_) | Error::InvalidRecordType => SPFResult::None, |
3595 | - Error::ParseError => SPFResult::PermError, |
3596 | - _ => SPFResult::TempError, |
3597 | + Error::DNSRecordNotFound(_) | Error::InvalidRecordType => SpfResult::None, |
3598 | + Error::ParseError => SpfResult::PermError, |
3599 | + _ => SpfResult::TempError, |
3600 | } |
3601 | } |
3602 | } |
3603 | @@ -509,8 +512,8 @@ mod test { |
3604 | |
3605 | use crate::{ |
3606 | common::parse::TxtRecordParser, |
3607 | - spf::{Macro, SPF}, |
3608 | - Resolver, SPFResult, MX, |
3609 | + spf::{Macro, Spf}, |
3610 | + Resolver, SpfResult, MX, |
3611 | }; |
3612 | |
3613 | #[tokio::test] |
3614 | @@ -548,7 +551,7 @@ mod test { |
3615 | let (name, record) = record.trim().split_once(' ').unwrap(); |
3616 | resolver.txt_add( |
3617 | name.trim().to_string(), |
3618 | - SPF::parse(record.as_bytes()), |
3619 | + Spf::parse(record.as_bytes()), |
3620 | valid_until, |
3621 | ); |
3622 | } else if let Some(record) = line.strip_prefix("exp:") { |
3623 | @@ -616,7 +619,7 @@ mod test { |
3624 | client_ip = value.trim().parse().unwrap(); |
3625 | } else if let Some(value) = line.strip_prefix("expect:") { |
3626 | let value = value.trim(); |
3627 | - let (result, exp): (SPFResult, &str) = |
3628 | + let (result, exp): (SpfResult, &str) = |
3629 | if let Some((result, exp)) = value.split_once(' ') { |
3630 | (result.trim().try_into().unwrap(), exp.trim()) |
3631 | } else { |