Skip to main content

mailrs_outbound_queue/
mta_sts.rs

1/// check if an MX hostname matches any pattern in the MTA-STS policy
2pub fn mx_matches_policy(mx_host: &str, policy_mx: &[&str]) -> bool {
3    let mx_lower = mx_host.to_lowercase();
4    for pattern in policy_mx {
5        let p = pattern.to_lowercase();
6        if p.starts_with("*.") {
7            // wildcard match: *.example.com matches mail.example.com
8            let suffix = &p[1..]; // ".example.com"
9            if mx_lower.ends_with(&suffix) && mx_lower.len() > suffix.len() {
10                // ensure the part before the suffix has no dots (single level)
11                let prefix = &mx_lower[..mx_lower.len() - suffix.len()];
12                if !prefix.contains('.') {
13                    return true;
14                }
15            }
16        } else if mx_lower == p {
17            return true;
18        }
19    }
20    false
21}
22
23#[cfg(test)]
24mod tests {
25    use super::*;
26
27    #[test]
28    fn exact_match() {
29        assert!(mx_matches_policy("mail.example.com", &["mail.example.com"]));
30    }
31
32    #[test]
33    fn wildcard_match() {
34        assert!(mx_matches_policy("mail.example.com", &["*.example.com"]));
35    }
36
37    #[test]
38    fn wildcard_no_match_different_domain() {
39        assert!(!mx_matches_policy("other.com", &["*.example.com"]));
40    }
41
42    #[test]
43    fn wildcard_no_match_subdomain() {
44        // *.example.com should NOT match sub.mail.example.com
45        assert!(!mx_matches_policy(
46            "sub.mail.example.com",
47            &["*.example.com"]
48        ));
49    }
50
51    #[test]
52    fn case_insensitive() {
53        assert!(mx_matches_policy("MAIL.Example.COM", &["*.example.com"]));
54    }
55
56    #[test]
57    fn no_match() {
58        assert!(!mx_matches_policy("mail.other.com", &["*.example.com"]));
59    }
60
61    #[test]
62    fn multiple_patterns() {
63        assert!(mx_matches_policy(
64            "backup.example.com",
65            &["mail.example.com", "*.example.com"]
66        ));
67    }
68
69    #[test]
70    fn empty_policy_never_matches() {
71        assert!(!mx_matches_policy("mail.example.com", &[]));
72    }
73
74    #[test]
75    fn wildcard_does_not_match_bare_domain() {
76        // *.example.com should not match "example.com" itself
77        assert!(!mx_matches_policy("example.com", &["*.example.com"]));
78    }
79
80    #[test]
81    fn exact_match_case_insensitive() {
82        assert!(mx_matches_policy("MAIL.EXAMPLE.COM", &["mail.example.com"]));
83        assert!(mx_matches_policy("mail.example.com", &["MAIL.EXAMPLE.COM"]));
84    }
85
86    #[test]
87    fn wildcard_matches_single_label_only() {
88        // only one label before the suffix may match
89        assert!(mx_matches_policy("a.example.com", &["*.example.com"]));
90        assert!(!mx_matches_policy("a.b.example.com", &["*.example.com"]));
91    }
92
93    #[test]
94    fn no_false_positive_partial_suffix() {
95        // "notexample.com" should not match "*.example.com"
96        assert!(!mx_matches_policy("mailnotexample.com", &["*.example.com"]));
97    }
98
99    #[test]
100    fn first_matching_pattern_wins() {
101        // result is true regardless of order when first pattern matches
102        assert!(mx_matches_policy(
103            "mail.example.com",
104            &["mail.example.com", "*.other.com"]
105        ));
106    }
107
108    #[test]
109    fn pattern_without_wildcard_no_partial_match() {
110        // "mail.example.com" should not match pattern "example.com"
111        assert!(!mx_matches_policy("mail.example.com", &["example.com"]));
112    }
113
114    #[test]
115    fn wildcard_pattern_case_insensitive_uppercase() {
116        assert!(mx_matches_policy("mail.example.com", &["*.EXAMPLE.COM"]));
117    }
118
119    #[test]
120    fn wildcard_pattern_mixed_case() {
121        assert!(mx_matches_policy("Mail.Example.Com", &["*.example.com"]));
122        assert!(mx_matches_policy("mail.example.com", &["*.Example.Com"]));
123    }
124
125    #[test]
126    fn wildcard_does_not_match_empty_prefix() {
127        // ".example.com" should not match *.example.com
128        // the prefix before the suffix must have length > 0, which the code handles via `mx_lower.len() > suffix.len()`
129        assert!(!mx_matches_policy(".example.com", &["*.example.com"]));
130    }
131
132    #[test]
133    fn exact_match_trailing_case_sensitivity() {
134        assert!(mx_matches_policy("mx1.gmail.com", &["mx1.gmail.com"]));
135        assert!(mx_matches_policy("MX1.GMAIL.COM", &["mx1.gmail.com"]));
136    }
137
138    #[test]
139    fn wildcard_single_char_prefix() {
140        assert!(mx_matches_policy("a.example.com", &["*.example.com"]));
141    }
142
143    #[test]
144    fn wildcard_long_prefix() {
145        assert!(mx_matches_policy(
146            "very-long-hostname-label.example.com",
147            &["*.example.com"]
148        ));
149    }
150
151    #[test]
152    fn wildcard_hyphenated_prefix() {
153        assert!(mx_matches_policy(
154            "mail-relay-01.example.com",
155            &["*.example.com"]
156        ));
157    }
158
159    #[test]
160    fn wildcard_numeric_prefix() {
161        assert!(mx_matches_policy("123.example.com", &["*.example.com"]));
162    }
163
164    #[test]
165    fn multiple_patterns_first_exact_wins() {
166        assert!(mx_matches_policy(
167            "mx.example.com",
168            &["mx.example.com", "*.other.com"]
169        ));
170    }
171
172    #[test]
173    fn multiple_patterns_second_wildcard_wins() {
174        assert!(mx_matches_policy(
175            "relay.other.com",
176            &["mx.example.com", "*.other.com"]
177        ));
178    }
179
180    #[test]
181    fn multiple_patterns_none_match() {
182        assert!(!mx_matches_policy(
183            "mail.unknown.org",
184            &["mx.example.com", "*.other.com"]
185        ));
186    }
187
188    #[test]
189    fn wildcard_with_deeper_subdomain_parent() {
190        // *.mail.example.com should match relay.mail.example.com
191        assert!(mx_matches_policy(
192            "relay.mail.example.com",
193            &["*.mail.example.com"]
194        ));
195    }
196
197    #[test]
198    fn wildcard_with_deeper_subdomain_no_double() {
199        // *.mail.example.com should NOT match a.b.mail.example.com
200        assert!(!mx_matches_policy(
201            "a.b.mail.example.com",
202            &["*.mail.example.com"]
203        ));
204    }
205
206    #[test]
207    fn wildcard_pattern_exact_domain_not_matched() {
208        // *.mail.example.com should NOT match mail.example.com itself
209        assert!(!mx_matches_policy(
210            "mail.example.com",
211            &["*.mail.example.com"]
212        ));
213    }
214
215    #[test]
216    fn pattern_with_only_wildcard_star_dot() {
217        // edge case: pattern "*.com" should match "anything.com"
218        assert!(mx_matches_policy("anything.com", &["*.com"]));
219    }
220
221    #[test]
222    fn pattern_star_dot_does_not_match_subdomain() {
223        // "*.com" should not match "a.b.com"
224        assert!(!mx_matches_policy("a.b.com", &["*.com"]));
225    }
226
227    #[test]
228    fn empty_mx_host_never_matches() {
229        assert!(!mx_matches_policy("", &["*.example.com"]));
230        assert!(!mx_matches_policy("", &["example.com"]));
231    }
232
233    #[test]
234    fn empty_mx_host_and_empty_policy() {
235        assert!(!mx_matches_policy("", &[]));
236    }
237
238    #[test]
239    fn exact_match_does_not_match_substring() {
240        // exact pattern "example.com" should not match "notexample.com"
241        assert!(!mx_matches_policy("notexample.com", &["example.com"]));
242    }
243
244    #[test]
245    fn exact_match_does_not_match_superdomain() {
246        assert!(!mx_matches_policy("example.com", &["sub.example.com"]));
247    }
248
249    #[test]
250    fn many_patterns_performance() {
251        // ensure linear scan with many patterns still works
252        let patterns: Vec<String> = (0..100)
253            .map(|i| format!("mx{}.example{}.com", i, i))
254            .collect();
255        let pattern_refs: Vec<&str> = patterns.iter().map(|s| s.as_str()).collect();
256        assert!(!mx_matches_policy("mx.unknown.com", &pattern_refs));
257        assert!(mx_matches_policy("mx50.example50.com", &pattern_refs));
258    }
259
260    #[test]
261    fn wildcard_many_patterns_last_matches() {
262        let mut patterns: Vec<String> =
263            (0..99).map(|i| format!("mx{}.wrong{}.com", i, i)).collect();
264        patterns.push("*.target.com".to_string());
265        let pattern_refs: Vec<&str> = patterns.iter().map(|s| s.as_str()).collect();
266        assert!(mx_matches_policy("relay.target.com", &pattern_refs));
267    }
268
269    #[test]
270    fn real_world_google_mx_patterns() {
271        // google's MTA-STS policy uses *.gmail-smtp-in.l.google.com
272        let policy = &["gmail-smtp-in.l.google.com", "*.gmail-smtp-in.l.google.com"];
273        assert!(mx_matches_policy("gmail-smtp-in.l.google.com", policy));
274        assert!(mx_matches_policy("alt1.gmail-smtp-in.l.google.com", policy));
275        // wildcard only matches single level — *.google.com would NOT match multi-level hostnames
276        let broad_policy = &["*.google.com"];
277        assert!(!mx_matches_policy(
278            "alt1.gmail-smtp-in.l.google.com",
279            broad_policy
280        ));
281    }
282
283    #[test]
284    fn real_world_microsoft_mx_patterns() {
285        let policy = &["*.mail.protection.outlook.com"];
286        assert!(mx_matches_policy(
287            "contoso-com.mail.protection.outlook.com",
288            policy
289        ));
290        assert!(!mx_matches_policy(
291            "a.b.mail.protection.outlook.com",
292            policy
293        ));
294    }
295}