maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package dmarc
 20
 21import (
 22	"bufio"
 23	"strings"
 24	"testing"
 25
 26	"github.com/emersion/go-message/textproto"
 27	"github.com/emersion/go-msgauth/authres"
 28	"github.com/emersion/go-msgauth/dmarc"
 29)
 30
 31func TestEvaluateAlignment(t *testing.T) {
 32	type tCase struct {
 33		fromDomain string
 34		record     *Record
 35		results    []authres.Result
 36
 37		output authres.ResultValue
 38	}
 39	test := func(i int, c tCase) {
 40		out := EvaluateAlignment(c.fromDomain, c.record, c.results)
 41		t.Logf("%d - %+v", i, out)
 42		if out.Authres.Value != c.output {
 43			t.Errorf("%d: Wrong eval result, want '%s', got '%s' (%+v)", i, c.output, out.Authres.Value, out)
 44		}
 45	}
 46
 47	cases := []tCase{
 48		{ // 0
 49			fromDomain: "example.org",
 50			record:     &Record{},
 51
 52			output: authres.ResultNone,
 53		},
 54		{ // 1
 55			fromDomain: "example.org",
 56			record:     &Record{},
 57			results: []authres.Result{
 58				&authres.SPFResult{
 59					Value: authres.ResultFail,
 60					From:  "example.org",
 61					Helo:  "mx.example.org",
 62				},
 63				&authres.DKIMResult{
 64					Value:  authres.ResultNone,
 65					Domain: "example.org",
 66				},
 67			},
 68			output: authres.ResultFail,
 69		},
 70		{ // 2
 71			fromDomain: "example.org",
 72			record:     &Record{},
 73			results: []authres.Result{
 74				&authres.SPFResult{
 75					Value: authres.ResultPass,
 76					From:  "example.org",
 77					Helo:  "mx.example.org",
 78				},
 79				&authres.DKIMResult{
 80					Value:  authres.ResultNone,
 81					Domain: "example.org",
 82				},
 83			},
 84			output: authres.ResultPass,
 85		},
 86		{ // 3
 87			fromDomain: "example.org",
 88			record:     &Record{},
 89			results: []authres.Result{
 90				&authres.SPFResult{
 91					Value: authres.ResultFail,
 92					From:  "example.org",
 93					Helo:  "mx.example.org",
 94				},
 95				&authres.DKIMResult{
 96					Value:  authres.ResultNone,
 97					Domain: "example.org",
 98				},
 99			},
100			output: authres.ResultFail,
101		},
102		{ // 4
103			fromDomain: "example.org",
104			record:     &Record{},
105			results: []authres.Result{
106				&authres.SPFResult{
107					Value: authres.ResultPass,
108					From:  "example.com",
109					Helo:  "mx.example.com",
110				},
111				&authres.DKIMResult{
112					Value:  authres.ResultNone,
113					Domain: "example.org",
114				},
115			},
116			output: authres.ResultFail,
117		},
118		{ // 5
119			fromDomain: "example.com",
120			record:     &Record{},
121			results: []authres.Result{
122				&authres.SPFResult{
123					Value: authres.ResultPass,
124					From:  "cbg.bounces.example.com",
125					Helo:  "mx.example.com",
126				},
127				&authres.DKIMResult{
128					Value:  authres.ResultNone,
129					Domain: "example.org",
130				},
131			},
132			output: authres.ResultPass,
133		},
134		{ // 6
135			fromDomain: "example.com",
136			record: &Record{
137				SPFAlignment: dmarc.AlignmentStrict,
138			},
139			results: []authres.Result{
140				&authres.SPFResult{
141					Value: authres.ResultPass,
142					From:  "cbg.bounces.example.com",
143					Helo:  "mx.example.com",
144				},
145				&authres.DKIMResult{
146					Value:  authres.ResultNone,
147					Domain: "example.org",
148				},
149			},
150			output: authres.ResultFail,
151		},
152		{ // 7
153			fromDomain: "example.org",
154			record:     &Record{},
155			results: []authres.Result{
156				&authres.DKIMResult{
157					Value:  authres.ResultFail,
158					Domain: "example.org",
159				},
160				&authres.SPFResult{
161					Value: authres.ResultNone,
162					From:  "example.org",
163					Helo:  "mx.example.org",
164				},
165			},
166			output: authres.ResultFail,
167		},
168		{ // 8
169			fromDomain: "example.org",
170			record:     &Record{},
171			results: []authres.Result{
172				&authres.DKIMResult{
173					Value:  authres.ResultPass,
174					Domain: "example.org",
175				},
176				&authres.SPFResult{
177					Value: authres.ResultNone,
178					From:  "example.org",
179					Helo:  "mx.example.org",
180				},
181			},
182			output: authres.ResultPass,
183		},
184		{ // 9
185			fromDomain: "example.com",
186			record:     &Record{},
187			results: []authres.Result{
188				&authres.SPFResult{
189					Value: authres.ResultPass,
190					From:  "cbg.bounces.example.com",
191					Helo:  "mx.example.com",
192				},
193				&authres.DKIMResult{
194					Value:  authres.ResultPass,
195					Domain: "example.com",
196				},
197			},
198			output: authres.ResultPass,
199		},
200		{ // 10
201			fromDomain: "example.com",
202			record: &Record{
203				SPFAlignment: dmarc.AlignmentRelaxed,
204			},
205			results: []authres.Result{
206				&authres.SPFResult{
207					Value: authres.ResultPass,
208					From:  "cbg.bounces.example.com",
209					Helo:  "mx.example.com",
210				},
211				&authres.DKIMResult{
212					Value:  authres.ResultFail,
213					Domain: "example.com",
214				},
215			},
216			output: authres.ResultPass,
217		},
218		{ // 11
219			fromDomain: "example.com",
220			record: &Record{
221				SPFAlignment: dmarc.AlignmentStrict,
222			},
223			results: []authres.Result{
224				&authres.SPFResult{
225					Value: authres.ResultPass,
226					From:  "cbg.bounces.example.com",
227					Helo:  "mx.example.com",
228				},
229				&authres.DKIMResult{
230					Value:  authres.ResultPass,
231					Domain: "example.com",
232				},
233			},
234			output: authres.ResultPass,
235		},
236		{ // 12
237			fromDomain: "example.com",
238			record: &Record{
239				SPFAlignment:  dmarc.AlignmentStrict,
240				DKIMAlignment: dmarc.AlignmentStrict,
241			},
242			results: []authres.Result{
243				&authres.SPFResult{
244					Value: authres.ResultPass,
245					From:  "cbg.bounces.example.com",
246					Helo:  "mx.example.com",
247				},
248				&authres.DKIMResult{
249					Value:  authres.ResultFail,
250					Domain: "cbg.example.com",
251				},
252			},
253			output: authres.ResultFail,
254		},
255		{ // 13
256			fromDomain: "example.org",
257			record:     &Record{},
258			results: []authres.Result{
259				&authres.DKIMResult{
260					Value:  authres.ResultFail,
261					Domain: "example.org",
262				},
263				&authres.DKIMResult{
264					Value:  authres.ResultPass,
265					Domain: "example.net",
266				},
267				&authres.DKIMResult{
268					Value:  authres.ResultPass,
269					Domain: "example.org",
270				},
271				&authres.DKIMResult{
272					Value:  authres.ResultFail,
273					Domain: "example.com",
274				},
275				&authres.SPFResult{
276					Value: authres.ResultNone,
277					From:  "example.org",
278					Helo:  "mx.example.org",
279				},
280			},
281			output: authres.ResultPass,
282		},
283		{ // 14
284			fromDomain: "example.com",
285			record:     &Record{},
286			results: []authres.Result{
287				&authres.SPFResult{
288					Value: authres.ResultPass,
289					From:  "",
290					Helo:  "mx.example.com",
291				},
292				&authres.DKIMResult{
293					Value:  authres.ResultNone,
294					Domain: "example.org",
295				},
296			},
297			output: authres.ResultPass,
298		},
299		{ // 15
300			fromDomain: "example.com",
301			record: &Record{
302				SPFAlignment: dmarc.AlignmentStrict,
303			},
304			results: []authres.Result{
305				&authres.SPFResult{
306					Value: authres.ResultPass,
307					From:  "",
308					Helo:  "mx.example.com",
309				},
310				&authres.DKIMResult{
311					Value:  authres.ResultNone,
312					Domain: "example.org",
313				},
314			},
315			output: authres.ResultFail,
316		},
317		{ // 16
318			fromDomain: "example.com",
319			record:     &Record{},
320			results: []authres.Result{
321				&authres.SPFResult{
322					Value: authres.ResultTempError,
323					From:  "",
324					Helo:  "mx.example.com",
325				},
326				&authres.DKIMResult{
327					Value:  authres.ResultNone,
328					Domain: "example.org",
329				},
330			},
331			output: authres.ResultTempError,
332		},
333		{ // 17
334			fromDomain: "example.com",
335			record:     &Record{},
336			results: []authres.Result{
337				&authres.DKIMResult{
338					Value:  authres.ResultTempError,
339					Domain: "example.com",
340				},
341				&authres.SPFResult{
342					Value: authres.ResultNone,
343					From:  "example.org",
344					Helo:  "mx.example.org",
345				},
346			},
347			output: authres.ResultTempError,
348		},
349		{ // 18
350			fromDomain: "example.com",
351			record:     &Record{},
352			results: []authres.Result{
353				&authres.SPFResult{
354					Value: authres.ResultTempError,
355					From:  "",
356					Helo:  "mx.example.com",
357				},
358				&authres.DKIMResult{
359					Value:  authres.ResultPass,
360					Domain: "example.com",
361				},
362			},
363			output: authres.ResultPass,
364		},
365		{ // 19
366			fromDomain: "example.com",
367			record:     &Record{},
368			results: []authres.Result{
369				&authres.SPFResult{
370					Value: authres.ResultPass,
371					From:  "",
372					Helo:  "mx.example.com",
373				},
374				&authres.DKIMResult{
375					Value:  authres.ResultTempError,
376					Domain: "example.com",
377				},
378			},
379			output: authres.ResultPass,
380		},
381		{ // 20
382			fromDomain: "example.org",
383			record:     &Record{},
384			results: []authres.Result{
385				&authres.DKIMResult{
386					Value:  authres.ResultPass,
387					Domain: "example.org",
388				},
389				&authres.DKIMResult{
390					Value:  authres.ResultTempError,
391					Domain: "example.org",
392				},
393				&authres.SPFResult{
394					Value: authres.ResultNone,
395					From:  "example.org",
396					Helo:  "mx.example.org",
397				},
398			},
399			output: authres.ResultPass,
400		},
401		{ // 21
402			fromDomain: "example.org",
403			record:     &Record{},
404			results: []authres.Result{
405				&authres.DKIMResult{
406					Value:  authres.ResultFail,
407					Domain: "example.org",
408				},
409				&authres.DKIMResult{
410					Value:  authres.ResultTempError,
411					Domain: "example.org",
412				},
413				&authres.SPFResult{
414					Value: authres.ResultNone,
415					From:  "example.org",
416					Helo:  "mx.example.org",
417				},
418			},
419			output: authres.ResultTempError,
420		},
421		{ // 22
422			fromDomain: "example.org",
423			record:     &Record{},
424			results: []authres.Result{
425				&authres.DKIMResult{
426					Value:  authres.ResultNone,
427					Domain: "example.org",
428				},
429				&authres.SPFResult{
430					Value: authres.ResultNone,
431					From:  "example.org",
432					Helo:  "mx.example.org",
433				},
434			},
435			output: authres.ResultFail,
436		},
437		{ // 23
438			fromDomain: "sub.example.org",
439			record:     &Record{},
440			results: []authres.Result{
441				&authres.DKIMResult{
442					Value:  authres.ResultPass,
443					Domain: "mx.example.org",
444				},
445				&authres.SPFResult{
446					Value: authres.ResultNone,
447					From:  "example.org",
448					Helo:  "mx.example.org",
449				},
450			},
451			output: authres.ResultPass,
452		},
453	}
454	for i, case_ := range cases {
455		test(i, case_)
456	}
457}
458
459func TestExtractDomains(t *testing.T) {
460	type tCase struct {
461		hdr string
462
463		fromDomain string
464	}
465	test := func(i int, c tCase) {
466		hdr, err := textproto.ReadHeader(bufio.NewReader(strings.NewReader(c.hdr + "\n\n")))
467		if err != nil {
468			panic(err)
469		}
470
471		domain, err := ExtractFromDomain(hdr)
472		if c.fromDomain == "" && err == nil {
473			t.Errorf("%d: expected failure, got fromDomain = %s", i, domain)
474			return
475		}
476		if c.fromDomain != "" && err != nil {
477			t.Errorf("%d: unexpected error: %v", i, err)
478			return
479		}
480		if domain != c.fromDomain {
481			t.Errorf("%d: want fromDomain = %v but got %s", i, c.fromDomain, domain)
482		}
483	}
484
485	cases := []tCase{
486		{
487			hdr:        `From: <test@example.org>`,
488			fromDomain: "example.org",
489		},
490		{
491			hdr:        `From: <test@foo.example.org>`,
492			fromDomain: "foo.example.org",
493		},
494		{
495			hdr: `From: <test@foo.example.org>, <test@bar.example.org>`,
496		},
497		{
498			hdr: `From: <test@foo.example.org>,
499From: <test@bar.example.org>`,
500		},
501		{
502			hdr: `From: <test@>`,
503		},
504		{
505			hdr: `From: `,
506		},
507		{
508			hdr: `From: foo`,
509		},
510	}
511	for i, case_ := range cases {
512		test(i, case_)
513	}
514}