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 msgpipeline
 20
 21import (
 22	"errors"
 23	"testing"
 24
 25	"github.com/emersion/go-message/textproto"
 26	"github.com/emersion/go-msgauth/authres"
 27	"github.com/foxcpp/maddy/framework/module"
 28	"github.com/foxcpp/maddy/internal/testutils"
 29)
 30
 31func TestMsgPipeline_Checks(t *testing.T) {
 32	target := testutils.Target{}
 33	check1, check2 := testutils.Check{}, testutils.Check{}
 34	d := MsgPipeline{
 35		msgpipelineCfg: msgpipelineCfg{
 36			globalChecks: []module.Check{&check1, &check2},
 37			perSource:    map[string]sourceBlock{},
 38			defaultSource: sourceBlock{
 39				perRcpt: map[string]*rcptBlock{},
 40				defaultRcpt: &rcptBlock{
 41					targets: []module.DeliveryTarget{&target},
 42				},
 43			},
 44		},
 45		Log: testutils.Logger(t, "msgpipeline"),
 46	}
 47
 48	testutils.DoTestDelivery(t, &d, "whatever@whatever", []string{"whatever@whatever"})
 49
 50	if len(target.Messages) != 1 {
 51		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
 52	}
 53	if target.Messages[0].MsgMeta.Quarantine {
 54		t.Fatalf("message is quarantined when it shouldn't")
 55	}
 56
 57	if check1.UnclosedStates != 0 || check2.UnclosedStates != 0 {
 58		t.Fatalf("checks state objects leak or double-closed, alive counters: %v, %v", check1.UnclosedStates, check2.UnclosedStates)
 59	}
 60}
 61
 62func TestMsgPipeline_AuthResults(t *testing.T) {
 63	target := testutils.Target{}
 64	check1, check2 := testutils.Check{
 65		BodyRes: module.CheckResult{
 66			AuthResult: []authres.Result{
 67				&authres.SPFResult{
 68					Value: authres.ResultFail,
 69					From:  "FROM",
 70					Helo:  "HELO",
 71				},
 72			},
 73		},
 74	}, testutils.Check{
 75		BodyRes: module.CheckResult{
 76			AuthResult: []authres.Result{
 77				&authres.SPFResult{
 78					Value: authres.ResultFail,
 79					From:  "FROM2",
 80					Helo:  "HELO2",
 81				},
 82			},
 83		},
 84	}
 85	d := MsgPipeline{
 86		msgpipelineCfg: msgpipelineCfg{
 87			globalChecks: []module.Check{&check1, &check2},
 88			perSource:    map[string]sourceBlock{},
 89			defaultSource: sourceBlock{
 90				perRcpt: map[string]*rcptBlock{},
 91				defaultRcpt: &rcptBlock{
 92					targets: []module.DeliveryTarget{&target},
 93				},
 94			},
 95		},
 96		Hostname: "TEST-HOST",
 97		Log:      testutils.Logger(t, "msgpipeline"),
 98	}
 99
100	testutils.DoTestDelivery(t, &d, "whatever@whatever", []string{"whatever@whatever"})
101
102	if len(target.Messages) != 1 {
103		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
104	}
105
106	authRes := target.Messages[0].Header.Get("Authentication-Results")
107	id, parsed, err := authres.Parse(authRes)
108	if err != nil {
109		t.Fatalf("failed to parse results")
110	}
111	if id != "TEST-HOST" {
112		t.Fatalf("wrong authres identifier")
113	}
114	if len(parsed) != 2 {
115		t.Fatalf("wrong amount of parts, want %d, got %d", 2, len(parsed))
116	}
117
118	var seen1, seen2 bool
119	for _, parts := range parsed {
120		spfPart, ok := parts.(*authres.SPFResult)
121		if !ok {
122			t.Fatalf("Not SPFResult")
123		}
124
125		if spfPart.From == "FROM" {
126			seen1 = true
127		}
128		if spfPart.From == "FROM2" {
129			seen2 = true
130		}
131	}
132
133	if !seen1 {
134		t.Fatalf("First authRes is missing")
135	}
136	if !seen2 {
137		t.Fatalf("Second authRes is missing")
138	}
139
140	if check1.UnclosedStates != 0 || check2.UnclosedStates != 0 {
141		t.Fatalf("checks state objects leak or double-closed, alive counters: %v, %v", check1.UnclosedStates, check2.UnclosedStates)
142	}
143}
144
145func TestMsgPipeline_Headers(t *testing.T) {
146	hdr1 := textproto.Header{}
147	hdr1.Add("HDR1", "1")
148	hdr2 := textproto.Header{}
149	hdr2.Add("HDR2", "2")
150
151	target := testutils.Target{}
152	check1, check2 := testutils.Check{
153		BodyRes: module.CheckResult{
154			Header: hdr1,
155		},
156	}, testutils.Check{
157		BodyRes: module.CheckResult{
158			Header: hdr2,
159		},
160	}
161	d := MsgPipeline{
162		msgpipelineCfg: msgpipelineCfg{
163			globalChecks: []module.Check{&check1, &check2},
164			perSource:    map[string]sourceBlock{},
165			defaultSource: sourceBlock{
166				perRcpt: map[string]*rcptBlock{},
167				defaultRcpt: &rcptBlock{
168					targets: []module.DeliveryTarget{&target},
169				},
170			},
171		},
172		Hostname: "TEST-HOST",
173		Log:      testutils.Logger(t, "msgpipeline"),
174	}
175
176	testutils.DoTestDelivery(t, &d, "whatever@whatever", []string{"whatever@whatever"})
177
178	if len(target.Messages) != 1 {
179		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
180	}
181
182	if target.Messages[0].Header.Get("HDR1") != "1" {
183		t.Fatalf("wrong HDR1 value, want %s, got %s", "1", target.Messages[0].Header.Get("HDR1"))
184	}
185	if target.Messages[0].Header.Get("HDR2") != "2" {
186		t.Fatalf("wrong HDR2 value, want %s, got %s", "1", target.Messages[0].Header.Get("HDR2"))
187	}
188
189	if check1.UnclosedStates != 0 || check2.UnclosedStates != 0 {
190		t.Fatalf("checks state objects leak or double-closed, alive counters: %v, %v", check1.UnclosedStates, check2.UnclosedStates)
191	}
192}
193
194func TestMsgPipeline_Globalcheck_Errors(t *testing.T) {
195	target := testutils.Target{}
196	check_ := testutils.Check{
197		InitErr:   errors.New("1"),
198		ConnRes:   module.CheckResult{Reject: true, Reason: errors.New("2")},
199		SenderRes: module.CheckResult{Reject: true, Reason: errors.New("3")},
200		RcptRes:   module.CheckResult{Reject: true, Reason: errors.New("4")},
201		BodyRes:   module.CheckResult{Reject: true, Reason: errors.New("5")},
202	}
203	d := MsgPipeline{
204		msgpipelineCfg: msgpipelineCfg{
205			globalChecks: []module.Check{&check_},
206			perSource:    map[string]sourceBlock{},
207			defaultSource: sourceBlock{
208				perRcpt: map[string]*rcptBlock{},
209				defaultRcpt: &rcptBlock{
210					targets: []module.DeliveryTarget{&target},
211				},
212			},
213		},
214		Hostname: "TEST-HOST",
215		Log:      testutils.Logger(t, "msgpipeline"),
216	}
217
218	t.Run("init err", func(t *testing.T) {
219		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
220		if err == nil {
221			t.Fatal("expected error")
222		}
223	})
224
225	check_.InitErr = nil
226
227	t.Run("conn err", func(t *testing.T) {
228		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
229		if err == nil {
230			t.Fatal("expected error")
231		}
232	})
233
234	check_.ConnRes.Reject = false
235
236	t.Run("mail from err", func(t *testing.T) {
237		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
238		if err == nil {
239			t.Fatal("expected error")
240		}
241	})
242
243	check_.SenderRes.Reject = false
244
245	t.Run("rcpt to err", func(t *testing.T) {
246		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
247		if err == nil {
248			t.Fatal("expected error")
249		}
250	})
251
252	check_.RcptRes.Reject = false
253
254	t.Run("body err", func(t *testing.T) {
255		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
256		if err == nil {
257			t.Fatal("expected error")
258		}
259	})
260
261	check_.BodyRes.Reject = false
262
263	t.Run("no err", func(t *testing.T) {
264		testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
265	})
266
267	if check_.UnclosedStates != 0 {
268		t.Fatalf("check state objects leak or double-closed, counters: %d", check_.UnclosedStates)
269	}
270}
271
272func TestMsgPipeline_SourceCheck_Errors(t *testing.T) {
273	target := testutils.Target{}
274	check_ := testutils.Check{
275		InitErr:   errors.New("1"),
276		ConnRes:   module.CheckResult{Reject: true, Reason: errors.New("2")},
277		SenderRes: module.CheckResult{Reject: true, Reason: errors.New("3")},
278		RcptRes:   module.CheckResult{Reject: true, Reason: errors.New("4")},
279		BodyRes:   module.CheckResult{Reject: true, Reason: errors.New("5")},
280	}
281	globalCheck := testutils.Check{}
282	d := MsgPipeline{
283		msgpipelineCfg: msgpipelineCfg{
284			globalChecks: []module.Check{&globalCheck},
285			perSource:    map[string]sourceBlock{},
286			defaultSource: sourceBlock{
287				perRcpt: map[string]*rcptBlock{},
288				checks:  []module.Check{&check_},
289				defaultRcpt: &rcptBlock{
290					targets: []module.DeliveryTarget{&target},
291				},
292			},
293		},
294		Hostname: "TEST-HOST",
295		Log:      testutils.Logger(t, "msgpipeline"),
296	}
297
298	t.Run("init err", func(t *testing.T) {
299		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
300		if err == nil {
301			t.Fatal("expected error")
302		}
303	})
304
305	check_.InitErr = nil
306
307	t.Run("conn err", func(t *testing.T) {
308		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
309		if err == nil {
310			t.Fatal("expected error")
311		}
312	})
313
314	check_.ConnRes.Reject = false
315
316	t.Run("mail from err", func(t *testing.T) {
317		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
318		if err == nil {
319			t.Fatal("expected error")
320		}
321	})
322
323	check_.SenderRes.Reject = false
324
325	t.Run("rcpt to err", func(t *testing.T) {
326		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
327		if err == nil {
328			t.Fatal("expected error")
329		}
330	})
331
332	check_.RcptRes.Reject = false
333
334	t.Run("body err", func(t *testing.T) {
335		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
336		if err == nil {
337			t.Fatal("expected error")
338		}
339	})
340
341	check_.BodyRes.Reject = false
342
343	t.Run("no err", func(t *testing.T) {
344		testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
345	})
346
347	if check_.UnclosedStates != 0 || globalCheck.UnclosedStates != 0 {
348		t.Fatalf("check state objects leak or double-closed, counters: %d, %d",
349			check_.UnclosedStates, globalCheck.UnclosedStates)
350	}
351}
352
353func TestMsgPipeline_RcptCheck_Errors(t *testing.T) {
354	target := testutils.Target{}
355	check_ := testutils.Check{
356		InitErr:   errors.New("1"),
357		ConnRes:   module.CheckResult{Reject: true, Reason: errors.New("2")},
358		SenderRes: module.CheckResult{Reject: true, Reason: errors.New("3")},
359		RcptRes:   module.CheckResult{Reject: true, Reason: errors.New("4")},
360		BodyRes:   module.CheckResult{Reject: true, Reason: errors.New("5")},
361
362		InstName: "err_check",
363	}
364	// Added to check whether it leaks.
365	globalCheck := testutils.Check{InstName: "global_check"}
366	sourceCheck := testutils.Check{InstName: "source_check"}
367	d := MsgPipeline{
368		msgpipelineCfg: msgpipelineCfg{
369			globalChecks: []module.Check{&globalCheck},
370			perSource:    map[string]sourceBlock{},
371			defaultSource: sourceBlock{
372				perRcpt: map[string]*rcptBlock{},
373				checks:  []module.Check{&check_},
374				defaultRcpt: &rcptBlock{
375					targets: []module.DeliveryTarget{&target},
376				},
377			},
378		},
379		Hostname: "TEST-HOST",
380		Log:      testutils.Logger(t, "msgpipeline"),
381	}
382
383	t.Run("init err", func(t *testing.T) {
384		d.Log = testutils.Logger(t, "msgpipeline")
385		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
386		if err == nil {
387			t.Fatal("expected error")
388		}
389
390		t.Log("!!!", check_.UnclosedStates)
391	})
392
393	check_.InitErr = nil
394
395	t.Run("conn err", func(t *testing.T) {
396		d.Log = testutils.Logger(t, "msgpipeline")
397		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
398		if err == nil {
399			t.Fatal("expected error")
400		}
401
402		t.Log("!!!", check_.UnclosedStates)
403	})
404
405	check_.ConnRes.Reject = false
406
407	t.Run("mail from err", func(t *testing.T) {
408		d.Log = testutils.Logger(t, "msgpipeline")
409		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
410		if err == nil {
411			t.Fatal("expected error")
412		}
413
414		t.Log("!!!", check_.UnclosedStates)
415	})
416
417	check_.SenderRes.Reject = false
418
419	t.Run("rcpt to err", func(t *testing.T) {
420		d.Log = testutils.Logger(t, "msgpipeline")
421		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
422		if err == nil {
423			t.Fatal("expected error")
424		}
425	})
426
427	check_.RcptRes.Reject = false
428
429	t.Run("body err", func(t *testing.T) {
430		d.Log = testutils.Logger(t, "msgpipeline")
431		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
432		if err == nil {
433			t.Fatal("expected error")
434		}
435	})
436
437	check_.BodyRes.Reject = false
438
439	t.Run("no err", func(t *testing.T) {
440		d.Log = testutils.Logger(t, "msgpipeline")
441		testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
442	})
443
444	if check_.UnclosedStates != 0 || sourceCheck.UnclosedStates != 0 || globalCheck.UnclosedStates != 0 {
445		t.Fatalf("check state objects leak or double-closed, counters: %d, %d, %d",
446			check_.UnclosedStates, sourceCheck.UnclosedStates, globalCheck.UnclosedStates)
447	}
448}