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	"context"
 23	"errors"
 24	"testing"
 25
 26	"github.com/emersion/go-message/textproto"
 27	"github.com/emersion/go-smtp"
 28	"github.com/foxcpp/maddy/framework/buffer"
 29	"github.com/foxcpp/maddy/framework/module"
 30	"github.com/foxcpp/maddy/internal/modify"
 31	"github.com/foxcpp/maddy/internal/testutils"
 32)
 33
 34func TestMsgPipeline_AllToTarget(t *testing.T) {
 35	target := testutils.Target{}
 36	d := MsgPipeline{
 37		msgpipelineCfg: msgpipelineCfg{
 38			perSource: map[string]sourceBlock{},
 39			defaultSource: sourceBlock{
 40				perRcpt: map[string]*rcptBlock{},
 41				defaultRcpt: &rcptBlock{
 42					targets: []module.DeliveryTarget{&target},
 43				},
 44			},
 45		},
 46		Log: testutils.Logger(t, "msgpipeline"),
 47	}
 48
 49	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
 50
 51	if len(target.Messages) != 1 {
 52		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
 53	}
 54
 55	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
 56}
 57
 58func TestMsgPipeline_PerSourceDomainSplit(t *testing.T) {
 59	orgTarget, comTarget := testutils.Target{InstName: "orgTarget"}, testutils.Target{InstName: "comTarget"}
 60	d := MsgPipeline{
 61		msgpipelineCfg: msgpipelineCfg{
 62			perSource: map[string]sourceBlock{
 63				"example.com": {
 64					perRcpt: map[string]*rcptBlock{},
 65					defaultRcpt: &rcptBlock{
 66						targets: []module.DeliveryTarget{&comTarget},
 67					},
 68				},
 69				"example.org": {
 70					perRcpt: map[string]*rcptBlock{},
 71					defaultRcpt: &rcptBlock{
 72						targets: []module.DeliveryTarget{&orgTarget},
 73					},
 74				},
 75			},
 76			defaultSource: sourceBlock{rejectErr: errors.New("default src block used")},
 77		},
 78		Log: testutils.Logger(t, "msgpipeline"),
 79	}
 80
 81	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
 82	testutils.DoTestDelivery(t, &d, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"})
 83
 84	if len(comTarget.Messages) != 1 {
 85		t.Fatalf("wrong amount of messages received for comTarget, want %d, got %d", 1, len(comTarget.Messages))
 86	}
 87	testutils.CheckTestMessage(t, &comTarget, 0, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
 88
 89	if len(orgTarget.Messages) != 1 {
 90		t.Fatalf("wrong amount of messages received for orgTarget, want %d, got %d", 1, len(orgTarget.Messages))
 91	}
 92	testutils.CheckTestMessage(t, &orgTarget, 0, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"})
 93}
 94
 95func TestMsgPipeline_SourceIn(t *testing.T) {
 96	tblTarget, comTarget := testutils.Target{InstName: "tblTarget"}, testutils.Target{InstName: "comTarget"}
 97	d := MsgPipeline{
 98		msgpipelineCfg: msgpipelineCfg{
 99			sourceIn: []sourceIn{
100				{
101					t:     testutils.Table{},
102					block: sourceBlock{rejectErr: errors.New("non-matching block was used")},
103				},
104				{
105					t:     testutils.Table{Err: errors.New("this one will fail")},
106					block: sourceBlock{rejectErr: errors.New("failing block was used")},
107				},
108				{
109					t: testutils.Table{
110						M: map[string]string{
111							"specific@example.com": "",
112						},
113					},
114					block: sourceBlock{
115						perRcpt: map[string]*rcptBlock{},
116						defaultRcpt: &rcptBlock{
117							targets: []module.DeliveryTarget{&tblTarget},
118						},
119					},
120				},
121			},
122			perSource: map[string]sourceBlock{
123				"example.com": {
124					perRcpt: map[string]*rcptBlock{},
125					defaultRcpt: &rcptBlock{
126						targets: []module.DeliveryTarget{&comTarget},
127					},
128				},
129			},
130			defaultSource: sourceBlock{rejectErr: errors.New("default src block used")},
131		},
132		Log: testutils.Logger(t, "msgpipeline"),
133	}
134
135	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt@example.com"})
136	testutils.DoTestDelivery(t, &d, "specific@example.com", []string{"rcpt@example.com"})
137
138	if len(comTarget.Messages) != 1 {
139		t.Fatalf("wrong amount of messages received for comTarget, want %d, got %d", 1, len(comTarget.Messages))
140	}
141	testutils.CheckTestMessage(t, &comTarget, 0, "sender@example.com", []string{"rcpt@example.com"})
142
143	if len(tblTarget.Messages) != 1 {
144		t.Fatalf("wrong amount of messages received for orgTarget, want %d, got %d", 1, len(tblTarget.Messages))
145	}
146	testutils.CheckTestMessage(t, &tblTarget, 0, "specific@example.com", []string{"rcpt@example.com"})
147}
148
149func TestMsgPipeline_EmptyMAILFROM(t *testing.T) {
150	target := testutils.Target{InstName: "target"}
151	d := MsgPipeline{
152		msgpipelineCfg: msgpipelineCfg{
153			perSource: map[string]sourceBlock{},
154			defaultSource: sourceBlock{
155				perRcpt: map[string]*rcptBlock{},
156				defaultRcpt: &rcptBlock{
157					targets: []module.DeliveryTarget{&target},
158				},
159			},
160		},
161		Log: testutils.Logger(t, "msgpipeline"),
162	}
163
164	testutils.DoTestDelivery(t, &d, "", []string{"rcpt1@example.com", "rcpt2@example.com"})
165
166	if len(target.Messages) != 1 {
167		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 1, len(target.Messages))
168	}
169	testutils.CheckTestMessage(t, &target, 0, "", []string{"rcpt1@example.com", "rcpt2@example.com"})
170}
171
172func TestMsgPipeline_EmptyMAILFROM_ExplicitDest(t *testing.T) {
173	target := testutils.Target{InstName: "target"}
174	d := MsgPipeline{
175		msgpipelineCfg: msgpipelineCfg{
176			perSource: map[string]sourceBlock{
177				"": {
178					perRcpt: map[string]*rcptBlock{},
179					defaultRcpt: &rcptBlock{
180						targets: []module.DeliveryTarget{&target},
181					},
182				},
183			},
184			defaultSource: sourceBlock{rejectErr: errors.New("default src block used")},
185		},
186		Log: testutils.Logger(t, "msgpipeline"),
187	}
188
189	testutils.DoTestDelivery(t, &d, "", []string{"rcpt1@example.com", "rcpt2@example.com"})
190
191	if len(target.Messages) != 1 {
192		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 1, len(target.Messages))
193	}
194	testutils.CheckTestMessage(t, &target, 0, "", []string{"rcpt1@example.com", "rcpt2@example.com"})
195}
196
197func TestMsgPipeline_PerRcptAddrSplit(t *testing.T) {
198	target1, target2 := testutils.Target{InstName: "target1"}, testutils.Target{InstName: "target2"}
199	d := MsgPipeline{
200		msgpipelineCfg: msgpipelineCfg{
201			perSource: map[string]sourceBlock{},
202			defaultSource: sourceBlock{
203				perRcpt: map[string]*rcptBlock{
204					"rcpt1@example.com": {
205						targets: []module.DeliveryTarget{&target1},
206					},
207					"rcpt2@example.com": {
208						targets: []module.DeliveryTarget{&target2},
209					},
210				},
211				defaultRcpt: &rcptBlock{
212					rejectErr: errors.New("defaultRcpt block used"),
213				},
214			},
215		},
216		Log: testutils.Logger(t, "msgpipeline"),
217	}
218
219	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com"})
220	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt2@example.com"})
221
222	if len(target1.Messages) != 1 {
223		t.Errorf("wrong amount of messages received for target1, want %d, got %d", 1, len(target1.Messages))
224	}
225	testutils.CheckTestMessage(t, &target1, 0, "sender@example.com", []string{"rcpt1@example.com"})
226
227	if len(target2.Messages) != 1 {
228		t.Errorf("wrong amount of messages received for target1, want %d, got %d", 1, len(target2.Messages))
229	}
230	testutils.CheckTestMessage(t, &target2, 0, "sender@example.com", []string{"rcpt2@example.com"})
231}
232
233func TestMsgPipeline_PerRcptDomainSplit(t *testing.T) {
234	target1, target2 := testutils.Target{InstName: "target1"}, testutils.Target{InstName: "target2"}
235	d := MsgPipeline{
236		msgpipelineCfg: msgpipelineCfg{
237			perSource: map[string]sourceBlock{},
238			defaultSource: sourceBlock{
239				perRcpt: map[string]*rcptBlock{
240					"example.com": {
241						targets: []module.DeliveryTarget{&target1},
242					},
243					"example.org": {
244						targets: []module.DeliveryTarget{&target2},
245					},
246				},
247				defaultRcpt: &rcptBlock{
248					rejectErr: errors.New("defaultRcpt block used"),
249				},
250			},
251		},
252		Log: testutils.Logger(t, "msgpipeline"),
253	}
254
255	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.org"})
256	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.org", "rcpt2@example.com"})
257
258	if len(target1.Messages) != 2 {
259		t.Errorf("wrong amount of messages received for target1, want %d, got %d", 2, len(target1.Messages))
260	}
261	testutils.CheckTestMessage(t, &target1, 0, "sender@example.com", []string{"rcpt1@example.com"})
262	testutils.CheckTestMessage(t, &target1, 1, "sender@example.com", []string{"rcpt2@example.com"})
263
264	if len(target2.Messages) != 2 {
265		t.Errorf("wrong amount of messages received for target2, want %d, got %d", 2, len(target2.Messages))
266	}
267	testutils.CheckTestMessage(t, &target2, 0, "sender@example.com", []string{"rcpt2@example.org"})
268	testutils.CheckTestMessage(t, &target2, 1, "sender@example.com", []string{"rcpt1@example.org"})
269}
270
271func TestMsgPipeline_DestInSplit(t *testing.T) {
272	target1, target2 := testutils.Target{InstName: "target1"}, testutils.Target{InstName: "target2"}
273	d := MsgPipeline{
274		msgpipelineCfg: msgpipelineCfg{
275			perSource: map[string]sourceBlock{},
276			defaultSource: sourceBlock{
277				rcptIn: []rcptIn{
278					{
279						t:     testutils.Table{},
280						block: &rcptBlock{rejectErr: errors.New("non-matching block was used")},
281					},
282					{
283						t:     testutils.Table{Err: errors.New("nope")},
284						block: &rcptBlock{rejectErr: errors.New("failing block was used")},
285					},
286					{
287						t: testutils.Table{
288							M: map[string]string{
289								"specific@example.com": "",
290							},
291						},
292						block: &rcptBlock{
293							targets: []module.DeliveryTarget{&target2},
294						},
295					},
296				},
297				perRcpt: map[string]*rcptBlock{
298					"example.com": {
299						targets: []module.DeliveryTarget{&target1},
300					},
301				},
302				defaultRcpt: &rcptBlock{
303					rejectErr: errors.New("defaultRcpt block used"),
304				},
305			},
306		},
307		Log: testutils.Logger(t, "msgpipeline"),
308	}
309
310	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "specific@example.com"})
311
312	if len(target1.Messages) != 1 {
313		t.Errorf("wrong amount of messages received for target1, want %d, got %d", 1, len(target1.Messages))
314	}
315	testutils.CheckTestMessage(t, &target1, 0, "sender@example.com", []string{"rcpt1@example.com"})
316
317	if len(target2.Messages) != 1 {
318		t.Errorf("wrong amount of messages received for target2, want %d, got %d", 1, len(target2.Messages))
319	}
320	testutils.CheckTestMessage(t, &target2, 0, "sender@example.com", []string{"specific@example.com"})
321}
322
323func TestMsgPipeline_PerSourceAddrAndDomainSplit(t *testing.T) {
324	target1, target2 := testutils.Target{InstName: "target1"}, testutils.Target{InstName: "target2"}
325	d := MsgPipeline{
326		msgpipelineCfg: msgpipelineCfg{
327			perSource: map[string]sourceBlock{
328				"sender1@example.com": {
329					perRcpt: map[string]*rcptBlock{},
330					defaultRcpt: &rcptBlock{
331						targets: []module.DeliveryTarget{&target1},
332					},
333				},
334				"example.com": {
335					perRcpt: map[string]*rcptBlock{},
336					defaultRcpt: &rcptBlock{
337						targets: []module.DeliveryTarget{&target2},
338					},
339				},
340			},
341			defaultSource: sourceBlock{rejectErr: errors.New("default src block used")},
342		},
343		Log: testutils.Logger(t, "msgpipeline"),
344	}
345
346	testutils.DoTestDelivery(t, &d, "sender1@example.com", []string{"rcpt@example.com"})
347	testutils.DoTestDelivery(t, &d, "sender2@example.com", []string{"rcpt@example.com"})
348
349	if len(target1.Messages) != 1 {
350		t.Fatalf("wrong amount of messages received for target1, want %d, got %d", 1, len(target1.Messages))
351	}
352	testutils.CheckTestMessage(t, &target1, 0, "sender1@example.com", []string{"rcpt@example.com"})
353
354	if len(target2.Messages) != 1 {
355		t.Fatalf("wrong amount of messages received for target2, want %d, got %d", 1, len(target2.Messages))
356	}
357	testutils.CheckTestMessage(t, &target2, 0, "sender2@example.com", []string{"rcpt@example.com"})
358}
359
360func TestMsgPipeline_PerSourceReject(t *testing.T) {
361	target := testutils.Target{}
362	d := MsgPipeline{
363		msgpipelineCfg: msgpipelineCfg{
364			perSource: map[string]sourceBlock{
365				"sender1@example.com": {
366					perRcpt: map[string]*rcptBlock{},
367					defaultRcpt: &rcptBlock{
368						targets: []module.DeliveryTarget{&target},
369					},
370				},
371				"example.com": {
372					perRcpt:   map[string]*rcptBlock{},
373					rejectErr: errors.New("go away"),
374				},
375			},
376			defaultSource: sourceBlock{rejectErr: errors.New("go away")},
377		},
378		Log: testutils.Logger(t, "msgpipeline"),
379	}
380
381	testutils.DoTestDelivery(t, &d, "sender1@example.com", []string{"rcpt@example.com"})
382
383	_, err := d.Start(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender2@example.com")
384	if err == nil {
385		t.Error("expected error for delivery.Start, got nil")
386	}
387
388	_, err = d.Start(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender2@example.org")
389	if err == nil {
390		t.Error("expected error for delivery.Start, got nil")
391	}
392}
393
394func TestMsgPipeline_PerRcptReject(t *testing.T) {
395	target := testutils.Target{}
396	d := MsgPipeline{
397		msgpipelineCfg: msgpipelineCfg{
398			perSource: map[string]sourceBlock{},
399			defaultSource: sourceBlock{
400				perRcpt: map[string]*rcptBlock{
401					"rcpt1@example.com": {
402						targets: []module.DeliveryTarget{&target},
403					},
404					"example.com": {
405						rejectErr: errors.New("go away"),
406					},
407				},
408				defaultRcpt: &rcptBlock{
409					rejectErr: errors.New("go away"),
410				},
411			},
412		},
413		Log: testutils.Logger(t, "msgpipeline"),
414	}
415
416	delivery, err := d.Start(context.Background(), &module.MsgMetadata{ID: "testing"}, "sender@example.com")
417	if err != nil {
418		t.Fatalf("unexpected Start err: %v", err)
419	}
420	defer func() {
421		if err := delivery.Abort(context.Background()); err != nil {
422			t.Fatalf("unexpected Abort err: %v", err)
423		}
424	}()
425
426	if err := delivery.AddRcpt(context.Background(), "rcpt2@example.com", smtp.RcptOptions{}); err == nil {
427		t.Fatalf("expected error for delivery.AddRcpt(rcpt2@example.com), got nil")
428	}
429	if err := delivery.AddRcpt(context.Background(), "rcpt1@example.com", smtp.RcptOptions{}); err != nil {
430		t.Fatalf("unexpected AddRcpt err for %s: %v", "rcpt1@example.com", err)
431	}
432	if err := delivery.Body(context.Background(), textproto.Header{}, buffer.MemoryBuffer{Slice: []byte("foobar")}); err != nil {
433		t.Fatalf("unexpected Body err: %v", err)
434	}
435	if err := delivery.Commit(context.Background()); err != nil {
436		t.Fatalf("unexpected Commit err: %v", err)
437	}
438}
439
440func TestMsgPipeline_PostmasterRcpt(t *testing.T) {
441	target := testutils.Target{}
442	d := MsgPipeline{
443		msgpipelineCfg: msgpipelineCfg{
444			perSource: map[string]sourceBlock{},
445			defaultSource: sourceBlock{
446				perRcpt: map[string]*rcptBlock{
447					"postmaster": {
448						targets: []module.DeliveryTarget{&target},
449					},
450					"example.com": {
451						rejectErr: errors.New("go away"),
452					},
453				},
454				defaultRcpt: &rcptBlock{
455					rejectErr: errors.New("go away"),
456				},
457			},
458		},
459		Log: testutils.Logger(t, "msgpipeline"),
460	}
461
462	testutils.DoTestDelivery(t, &d, "disappointed-user@example.com", []string{"postmaster"})
463	if len(target.Messages) != 1 {
464		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 1, len(target.Messages))
465	}
466	testutils.CheckTestMessage(t, &target, 0, "disappointed-user@example.com", []string{"postmaster"})
467}
468
469func TestMsgPipeline_PostmasterSrc(t *testing.T) {
470	target := testutils.Target{}
471	d := MsgPipeline{
472		msgpipelineCfg: msgpipelineCfg{
473			perSource: map[string]sourceBlock{
474				"postmaster": {
475					perRcpt: map[string]*rcptBlock{},
476					defaultRcpt: &rcptBlock{
477						targets: []module.DeliveryTarget{&target},
478					},
479				},
480				"example.com": {
481					rejectErr: errors.New("go away"),
482				},
483			},
484			defaultSource: sourceBlock{
485				rejectErr: errors.New("go away"),
486			},
487		},
488		Log: testutils.Logger(t, "msgpipeline"),
489	}
490
491	testutils.DoTestDelivery(t, &d, "postmaster", []string{"disappointed-user@example.com"})
492	if len(target.Messages) != 1 {
493		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 1, len(target.Messages))
494	}
495	testutils.CheckTestMessage(t, &target, 0, "postmaster", []string{"disappointed-user@example.com"})
496}
497
498func TestMsgPipeline_CaseInsensetiveMatch_Src(t *testing.T) {
499	target := testutils.Target{}
500	d := MsgPipeline{
501		msgpipelineCfg: msgpipelineCfg{
502			perSource: map[string]sourceBlock{
503				"postmaster": {
504					perRcpt: map[string]*rcptBlock{},
505					defaultRcpt: &rcptBlock{
506						targets: []module.DeliveryTarget{&target},
507					},
508				},
509				"sender@example.com": {
510					perRcpt: map[string]*rcptBlock{},
511					defaultRcpt: &rcptBlock{
512						targets: []module.DeliveryTarget{&target},
513					},
514				},
515				"example.com": {
516					perRcpt: map[string]*rcptBlock{},
517					defaultRcpt: &rcptBlock{
518						targets: []module.DeliveryTarget{&target},
519					},
520				},
521			},
522			defaultSource: sourceBlock{
523				rejectErr: errors.New("go away"),
524			},
525		},
526		Log: testutils.Logger(t, "msgpipeline"),
527	}
528
529	testutils.DoTestDelivery(t, &d, "POSTMastER", []string{"disappointed-user@example.com"})
530	testutils.DoTestDelivery(t, &d, "SenDeR@EXAMPLE.com", []string{"disappointed-user@example.com"})
531	testutils.DoTestDelivery(t, &d, "sender@exAMPle.com", []string{"disappointed-user@example.com"})
532	if len(target.Messages) != 3 {
533		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 3, len(target.Messages))
534	}
535	testutils.CheckTestMessage(t, &target, 0, "POSTMastER", []string{"disappointed-user@example.com"})
536	testutils.CheckTestMessage(t, &target, 1, "SenDeR@EXAMPLE.com", []string{"disappointed-user@example.com"})
537	testutils.CheckTestMessage(t, &target, 2, "sender@exAMPle.com", []string{"disappointed-user@example.com"})
538}
539
540func TestMsgPipeline_CaseInsensetiveMatch_Rcpt(t *testing.T) {
541	target := testutils.Target{}
542	d := MsgPipeline{
543		msgpipelineCfg: msgpipelineCfg{
544			perSource: map[string]sourceBlock{},
545			defaultSource: sourceBlock{
546				perRcpt: map[string]*rcptBlock{
547					"postmaster": {
548						targets: []module.DeliveryTarget{&target},
549					},
550					"sender@example.com": {
551						targets: []module.DeliveryTarget{&target},
552					},
553					"example.com": {
554						targets: []module.DeliveryTarget{&target},
555					},
556				},
557				defaultRcpt: &rcptBlock{
558					rejectErr: errors.New("wtf"),
559				},
560			},
561		},
562		Log: testutils.Logger(t, "msgpipeline"),
563	}
564
565	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"POSTMastER"})
566	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"SenDeR@EXAMPLE.com"})
567	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"sender@exAMPle.com"})
568	if len(target.Messages) != 3 {
569		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 3, len(target.Messages))
570	}
571	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"POSTMastER"})
572	testutils.CheckTestMessage(t, &target, 1, "sender@example.com", []string{"SenDeR@EXAMPLE.com"})
573	testutils.CheckTestMessage(t, &target, 2, "sender@example.com", []string{"sender@exAMPle.com"})
574}
575
576func TestMsgPipeline_UnicodeNFC_Rcpt(t *testing.T) {
577	target := testutils.Target{}
578	d := MsgPipeline{
579		msgpipelineCfg: msgpipelineCfg{
580			perSource: map[string]sourceBlock{},
581			defaultSource: sourceBlock{
582				perRcpt: map[string]*rcptBlock{
583					"rcpt@é.example.com": {
584						targets: []module.DeliveryTarget{&target},
585					},
586					"é.example.com": {
587						targets: []module.DeliveryTarget{&target},
588					},
589				},
590				defaultRcpt: &rcptBlock{
591					rejectErr: errors.New("wtf"),
592				},
593			},
594		},
595		Log: testutils.Logger(t, "msgpipeline"),
596	}
597
598	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt@E\u0301.EXAMPLE.com"})
599	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"f@E\u0301.exAMPle.com"})
600	if len(target.Messages) != 2 {
601		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 2, len(target.Messages))
602	}
603	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt@E\u0301.EXAMPLE.com"})
604	testutils.CheckTestMessage(t, &target, 1, "sender@example.com", []string{"f@E\u0301.exAMPle.com"})
605}
606
607func TestMsgPipeline_MalformedSource(t *testing.T) {
608	target := testutils.Target{}
609	d := MsgPipeline{
610		msgpipelineCfg: msgpipelineCfg{
611			perSource: map[string]sourceBlock{},
612			defaultSource: sourceBlock{
613				perRcpt: map[string]*rcptBlock{
614					"postmaster": {
615						targets: []module.DeliveryTarget{&target},
616					},
617					"sender@example.com": {
618						targets: []module.DeliveryTarget{&target},
619					},
620					"example.com": {
621						targets: []module.DeliveryTarget{&target},
622					},
623				},
624			},
625		},
626		Log: testutils.Logger(t, "msgpipeline"),
627	}
628
629	// Simple checks for violations that can make msgpipeline misbehave.
630	for _, addr := range []string{"not_postmaster_but_no_at_sign", "@no_mailbox", "no_domain@"} {
631		_, err := d.Start(context.Background(), &module.MsgMetadata{ID: "testing"}, addr)
632		if err == nil {
633			t.Errorf("%s is accepted as valid address", addr)
634		}
635	}
636}
637
638func TestMsgPipeline_TwoRcptToOneTarget(t *testing.T) {
639	target := testutils.Target{}
640	d := MsgPipeline{
641		msgpipelineCfg: msgpipelineCfg{
642			perSource: map[string]sourceBlock{},
643			defaultSource: sourceBlock{
644				perRcpt: map[string]*rcptBlock{
645					"example.com": {
646						targets: []module.DeliveryTarget{&target},
647					},
648					"example.org": {
649						targets: []module.DeliveryTarget{&target},
650					},
651				},
652			},
653		},
654		Log: testutils.Logger(t, "msgpipeline"),
655	}
656
657	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"recipient@example.com", "recipient@example.org"})
658
659	if len(target.Messages) != 1 {
660		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 1, len(target.Messages))
661	}
662	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"recipient@example.com", "recipient@example.org"})
663}
664
665func TestMsgPipeline_multi_alias(t *testing.T) {
666	target1, target2 := testutils.Target{InstName: "target1"}, testutils.Target{InstName: "target2"}
667	mod := testutils.Modifier{
668		RcptTo: map[string][]string{
669			"recipient@example.com": []string{
670				"recipient-1@example.org",
671				"recipient-2@example.net",
672			},
673		},
674	}
675	d := MsgPipeline{
676		msgpipelineCfg: msgpipelineCfg{
677			perSource: map[string]sourceBlock{},
678			defaultSource: sourceBlock{
679				modifiers: modify.Group{
680					Modifiers: []module.Modifier{mod},
681				},
682				perRcpt: map[string]*rcptBlock{
683					"example.org": {
684						targets: []module.DeliveryTarget{&target1},
685					},
686					"example.net": {
687						targets: []module.DeliveryTarget{&target2},
688					},
689				},
690			},
691		},
692		Log: testutils.Logger(t, "msgpipeline"),
693	}
694
695	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"recipient@example.com"})
696
697	if len(target1.Messages) != 1 {
698		t.Errorf("wrong amount of messages received for target1, want %d, got %d", 1, len(target1.Messages))
699	}
700	testutils.CheckTestMessage(t, &target1, 0, "sender@example.com", []string{"recipient-1@example.org"})
701
702	if len(target2.Messages) != 1 {
703		t.Errorf("wrong amount of messages received for target1, want %d, got %d", 1, len(target2.Messages))
704	}
705	testutils.CheckTestMessage(t, &target2, 0, "sender@example.com", []string{"recipient-2@example.net"})
706}