1package message
2
3import (
4 "testing"
5
6 "github.com/mjl-/mox/dns"
7)
8
9func TestAuthResultsPack(t *testing.T) {
10 dom, err := dns.ParseDomain("møx.example")
11 if err != nil {
12 t.Fatalf("parsing domain: %v", err)
13 }
14 authRes := AuthResults{
15 Hostname: dom.XName(true),
16 Comment: dom.ASCIIExtra(true),
17 Methods: []AuthMethod{
18 {"dkim", "", "pass", "", "", []AuthProp{{"header", "d", dom.XName(true), true, dom.ASCIIExtra(true)}}},
19 },
20 }
21 s := authRes.Header()
22 const exp = "Authentication-Results: (xn--mx-lka.example) møx.example;\r\n\tdkim=pass header.d=møx.example (xn--mx-lka.example)\r\n"
23 if s != exp {
24 t.Fatalf("got %q, expected %q", s, exp)
25 }
26}
27
28func TestAuthResultsParse(t *testing.T) {
29 ar, err := ParseAuthResults("(xn--mx-lka.example) møx.example;\r\n\tdkim=pass header.d=møx.example (xn--mx-lka.example)\r\n")
30 tcheck(t, err, "parsing auth results header")
31 tcompare(t, ar, AuthResults{
32 Hostname: "møx.example",
33 Methods: []AuthMethod{
34 {
35 Method: "dkim",
36 Result: "pass",
37 Props: []AuthProp{
38 {Type: "header", Property: "d", Value: "møx.example"},
39 },
40 },
41 },
42 })
43
44 const localhost = `localhost;
45 auth=pass smtp.mailfrom=mox+qvpVtG6ZQg-vJmN_beaGyQ@localhost
46`
47 ar, err = ParseAuthResults(localhost)
48 tcheck(t, err, "parsing auth results header")
49 tcompare(t, ar, AuthResults{
50 Hostname: "localhost",
51 Methods: []AuthMethod{
52 {
53 Method: "auth",
54 Result: "pass",
55 Props: []AuthProp{
56 {Type: "smtp", Property: "mailfrom", IsAddrLike: true, Value: "mox+qvpVtG6ZQg-vJmN_beaGyQ@localhost"},
57 },
58 },
59 },
60 })
61
62 const other = `komijn.test.xmox.nl;
63 iprev=pass (without dnssec) policy.iprev=198.2.145.102;
64 dkim=pass (2048 bit rsa, without dnssec) header.d=mandrillapp.com
65 header.s=mte1 header.a=rsa-sha256 header.b="CfNW8cht1/v3";
66 dkim=pass (2048 bit rsa, without dnssec) header.d=letsencrypt.org
67 header.s=mte1 header.a=rsa-sha256 header.b=F9lCi4OC77su
68 header.i=expiry@letsencrypt.org;
69 spf=pass (without dnssec) smtp.mailfrom=delivery.letsencrypt.org;
70 dmarc=pass (without dnssec) header.from=letsencrypt.org
71`
72
73 ar, err = ParseAuthResults(other)
74 tcheck(t, err, "parsing auth results header")
75 tcompare(t, ar, AuthResults{
76 Hostname: "komijn.test.xmox.nl",
77 Methods: []AuthMethod{
78 {
79 Method: "iprev",
80 Result: "pass",
81 Props: []AuthProp{
82 {Type: "policy", Property: "iprev", Value: "198.2.145.102"},
83 },
84 },
85 {
86 Method: "dkim",
87 Result: "pass",
88 Props: []AuthProp{
89 {Type: "header", Property: "d", Value: "mandrillapp.com"},
90 {Type: "header", Property: "s", Value: "mte1"},
91 {Type: "header", Property: "a", Value: "rsa-sha256"},
92 {Type: "header", Property: "b", Value: "CfNW8cht1/v3"},
93 },
94 },
95 {
96 Method: "dkim",
97 Result: "pass",
98 Props: []AuthProp{
99 {Type: "header", Property: "d", Value: "letsencrypt.org"},
100 {Type: "header", Property: "s", Value: "mte1"},
101 {Type: "header", Property: "a", Value: "rsa-sha256"},
102 {Type: "header", Property: "b", Value: "F9lCi4OC77su"},
103 {Type: "header", Property: "i", IsAddrLike: true, Value: "expiry@letsencrypt.org"},
104 },
105 },
106 {
107 Method: "spf",
108 Result: "pass",
109 Props: []AuthProp{
110 {Type: "smtp", Property: "mailfrom", Value: "delivery.letsencrypt.org"},
111 },
112 },
113 {
114 Method: "dmarc",
115 Result: "pass",
116 Props: []AuthProp{
117 {Type: "header", Property: "from", Value: "letsencrypt.org"},
118 },
119 },
120 },
121 })
122
123 const google = `mx.google.com;
124 dkim=pass header.i=@test.xmox.nl header.s=2022b header.b="Z9k/yZIA";
125 spf=pass (google.com: domain of mjl@test.xmox.nl designates 2a02:2770::21a:4aff:feba:bde0 as permitted sender) smtp.mailfrom=mjl@test.xmox.nl;
126 dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=test.xmox.nl
127`
128
129 ar, err = ParseAuthResults(google)
130 tcheck(t, err, "parsing auth results header")
131 tcompare(t, ar, AuthResults{
132 Hostname: "mx.google.com",
133 Methods: []AuthMethod{
134 {
135 Method: "dkim",
136 Result: "pass",
137 Props: []AuthProp{
138 {Type: "header", Property: "i", IsAddrLike: true, Value: "@test.xmox.nl"},
139 {Type: "header", Property: "s", Value: "2022b"},
140 {Type: "header", Property: "b", Value: "Z9k/yZIA"},
141 },
142 },
143 {
144 Method: "spf",
145 Result: "pass",
146 Props: []AuthProp{
147 {Type: "smtp", Property: "mailfrom", IsAddrLike: true, Value: "mjl@test.xmox.nl"},
148 },
149 },
150 {
151 Method: "dmarc",
152 Result: "pass",
153 Props: []AuthProp{
154 {Type: "header", Property: "from", Value: "test.xmox.nl"},
155 },
156 },
157 },
158 })
159
160 const yahoo = `atlas220.free.mail.bf1.yahoo.com;
161 dkim=perm_fail header.i=@ueber.net header.s=2023a;
162 dkim=pass header.i=@ueber.net header.s=2023b;
163 spf=pass smtp.mailfrom=ueber.net;
164 dmarc=pass(p=REJECT) header.from=ueber.net;
165`
166 ar, err = ParseAuthResults(yahoo)
167 tcheck(t, err, "parsing auth results header")
168 tcompare(t, ar, AuthResults{
169 Hostname: "atlas220.free.mail.bf1.yahoo.com",
170 Methods: []AuthMethod{
171 {
172 Method: "dkim",
173 Result: "perm_fail",
174 Props: []AuthProp{
175 {Type: "header", Property: "i", IsAddrLike: true, Value: "@ueber.net"},
176 {Type: "header", Property: "s", Value: "2023a"},
177 },
178 },
179 {
180 Method: "dkim",
181 Result: "pass",
182 Props: []AuthProp{
183 {Type: "header", Property: "i", IsAddrLike: true, Value: "@ueber.net"},
184 {Type: "header", Property: "s", Value: "2023b"},
185 },
186 },
187 {
188 Method: "spf",
189 Result: "pass",
190 Props: []AuthProp{
191 {Type: "smtp", Property: "mailfrom", Value: "ueber.net"},
192 },
193 },
194 {
195 Method: "dmarc",
196 Result: "pass",
197 Props: []AuthProp{
198 {Type: "header", Property: "from", Value: "ueber.net"},
199 },
200 },
201 },
202 })
203
204 const proton0 = `mail.protonmail.ch; dkim=pass (Good
205 ed25519-sha256 signature) header.d=ueber.net header.i=mechiel@ueber.net
206 header.a=ed25519-sha256; dkim=pass (Good 2048 bit rsa-sha256 signature)
207 header.d=ueber.net header.i=mechiel@ueber.net header.a=rsa-sha256
208`
209 ar, err = ParseAuthResults(proton0)
210 tcheck(t, err, "parsing auth results header")
211 tcompare(t, ar, AuthResults{
212 Hostname: "mail.protonmail.ch",
213 Methods: []AuthMethod{
214 {
215 Method: "dkim",
216 Result: "pass",
217 Props: []AuthProp{
218 {Type: "header", Property: "d", Value: "ueber.net"},
219 {Type: "header", Property: "i", IsAddrLike: true, Value: "mechiel@ueber.net"},
220 {Type: "header", Property: "a", Value: "ed25519-sha256"},
221 },
222 },
223 {
224 Method: "dkim",
225 Result: "pass",
226 Props: []AuthProp{
227 {Type: "header", Property: "d", Value: "ueber.net"},
228 {Type: "header", Property: "i", IsAddrLike: true, Value: "mechiel@ueber.net"},
229 {Type: "header", Property: "a", Value: "rsa-sha256"},
230 },
231 },
232 },
233 })
234
235 const proton1 = `mail.protonmail.ch; dmarc=pass (p=reject dis=none)
236 header.from=ueber.net
237`
238 ar, err = ParseAuthResults(proton1)
239 tcheck(t, err, "parsing auth results header")
240 tcompare(t, ar, AuthResults{
241 Hostname: "mail.protonmail.ch",
242 Methods: []AuthMethod{
243 {
244 Method: "dmarc",
245 Result: "pass",
246 Props: []AuthProp{
247 {Type: "header", Property: "from", Value: "ueber.net"},
248 },
249 },
250 },
251 })
252 const proton2 = `mail.protonmail.ch; spf=pass smtp.mailfrom=ueber.net
253`
254 ar, err = ParseAuthResults(proton2)
255 tcheck(t, err, "parsing auth results header")
256 tcompare(t, ar, AuthResults{
257 Hostname: "mail.protonmail.ch",
258 Methods: []AuthMethod{
259 {
260 Method: "spf",
261 Result: "pass",
262 Props: []AuthProp{
263 {Type: "smtp", Property: "mailfrom", Value: "ueber.net"},
264 },
265 },
266 },
267 })
268 const proton3 = `mail.protonmail.ch; arc=none smtp.remote-ip=84.22.96.237
269`
270 ar, err = ParseAuthResults(proton3)
271 tcheck(t, err, "parsing auth results header")
272 tcompare(t, ar, AuthResults{
273 Hostname: "mail.protonmail.ch",
274 Methods: []AuthMethod{
275 {
276 Method: "arc",
277 Result: "none",
278 Props: []AuthProp{
279 {Type: "smtp", Property: "remote-ip", Value: "84.22.96.237"},
280 },
281 },
282 },
283 })
284 const proton4 = `mail.protonmail.ch; dkim=permerror (0-bit key) header.d=ueber.net
285 header.i=mechiel@ueber.net header.b="a4SMWyJ7"; dkim=pass (2048-bit key)
286 header.d=ueber.net header.i=mechiel@ueber.net header.b="mQickWQ7"
287`
288 ar, err = ParseAuthResults(proton4)
289 tcheck(t, err, "parsing auth results header")
290 tcompare(t, ar, AuthResults{
291 Hostname: "mail.protonmail.ch",
292 Methods: []AuthMethod{
293 {
294 Method: "dkim",
295 Result: "permerror",
296 Props: []AuthProp{
297 {Type: "header", Property: "d", Value: "ueber.net"},
298 {Type: "header", Property: "i", IsAddrLike: true, Value: "mechiel@ueber.net"},
299 {Type: "header", Property: "b", Value: "a4SMWyJ7"},
300 },
301 },
302 {
303 Method: "dkim",
304 Result: "pass",
305 Props: []AuthProp{
306 {Type: "header", Property: "d", Value: "ueber.net"},
307 {Type: "header", Property: "i", IsAddrLike: true, Value: "mechiel@ueber.net"},
308 {Type: "header", Property: "b", Value: "mQickWQ7"},
309 },
310 },
311 },
312 })
313
314 const dkimReason = `host.example;
315 dkim=none reason="no dkim signatures"
316`
317 ar, err = ParseAuthResults(dkimReason)
318 tcheck(t, err, "parsing auth results header")
319 tcompare(t, ar, AuthResults{
320 Hostname: "host.example",
321 Methods: []AuthMethod{
322 {
323 Method: "dkim",
324 Result: "none",
325 Reason: "no dkim signatures",
326 },
327 },
328 })
329
330 // Outlook adds an invalid line, missing required hostname at the start. And their
331 // dmarc "action=none" is invalid. Nothing to be done.
332 const outlook = `x; spf=pass (sender IP is 84.22.96.237)
333 smtp.mailfrom=ueber.net; dkim=pass (signature was verified)
334 header.d=ueber.net;dmarc=pass action=none header.from=ueber.net;compauth=pass
335 reason=100
336`
337 _, err = ParseAuthResults(outlook)
338 if err == nil {
339 t.Fatalf("missing error while parsing authresults header from outlook")
340 }
341}
342