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 remote
  20
  21import (
  22	"context"
  23	"crypto/tls"
  24	"flag"
  25	"math/rand"
  26	"net"
  27	"os"
  28	"strconv"
  29	"testing"
  30
  31	"github.com/emersion/go-message/textproto"
  32	"github.com/emersion/go-smtp"
  33	"github.com/foxcpp/go-mockdns"
  34	"github.com/foxcpp/go-mtasts"
  35	"github.com/foxcpp/maddy/framework/buffer"
  36	"github.com/foxcpp/maddy/framework/config"
  37	"github.com/foxcpp/maddy/framework/dns"
  38	"github.com/foxcpp/maddy/framework/exterrors"
  39	"github.com/foxcpp/maddy/framework/module"
  40	"github.com/foxcpp/maddy/internal/limits"
  41	"github.com/foxcpp/maddy/internal/smtpconn/pool"
  42	"github.com/foxcpp/maddy/internal/testutils"
  43)
  44
  45// .invalid TLD is used here to make sure if there is something wrong about
  46// DNS hooks and lookups go to the real Internet, they will not result in
  47// any useful data that can lead to outgoing connections being made.
  48
  49func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver *dns.ExtResolver,
  50	extraPolicies []module.MXAuthPolicy) *Target {
  51	resolver := &mockdns.Resolver{Zones: zones}
  52
  53	tgt := Target{
  54		name:        "remote",
  55		hostname:    "mx.example.com",
  56		resolver:    resolver,
  57		dialer:      resolver.DialContext,
  58		extResolver: extResolver,
  59		tlsConfig:   &tls.Config{},
  60		Log:         testutils.Logger(t, "remote"),
  61		policies:    extraPolicies,
  62		limits:      &limits.Group{},
  63		pool: pool.New(pool.Config{
  64			MaxKeys:             5000,
  65			MaxConnsPerKey:      5,      // basically, max. amount of idle connections in cache
  66			MaxConnLifetimeSec:  150,    // 2.5 mins, half of recommended idle time from RFC 5321
  67			StaleKeyLifetimeSec: 60 * 5, // should be bigger than MaxConnLifetimeSec
  68		}),
  69	}
  70
  71	return &tgt
  72}
  73
  74func testSTSPolicy(t *testing.T, zones map[string]mockdns.Zone, mtastsGet func(context.Context, string) (*mtasts.Policy, error)) *mtastsPolicy {
  75	m, err := NewMTASTSPolicy("mx_auth.mtasts", "test", nil, nil)
  76	if err != nil {
  77		t.Fatal(err)
  78	}
  79	p := m.(*mtastsPolicy)
  80	err = p.Init(config.NewMap(nil, config.Node{
  81		Children: []config.Node{
  82			{
  83				Name: "cache",
  84				Args: []string{"ram"},
  85			},
  86		},
  87	}))
  88	if err != nil {
  89		t.Fatal(err)
  90	}
  91
  92	p.mtastsGet = mtastsGet
  93	p.log = testutils.Logger(t, "remote/mtasts")
  94	p.cache.Resolver = &mockdns.Resolver{Zones: zones}
  95	p.StartUpdater()
  96
  97	return p
  98}
  99
 100func testDANEPolicy(t *testing.T, extR *dns.ExtResolver) *danePolicy {
 101	m, err := NewDANEPolicy("mx_auth.dane", "test", nil, nil)
 102	if err != nil {
 103		t.Fatal(err)
 104	}
 105	p := m.(*danePolicy)
 106	err = p.Init(config.NewMap(nil, config.Node{
 107		Children: nil,
 108	}))
 109	if err != nil {
 110		t.Fatal(err)
 111	}
 112
 113	p.extResolver = extR
 114	p.log = testutils.Logger(t, "remote/dane")
 115	return p
 116}
 117
 118func TestRemoteDelivery(t *testing.T) {
 119	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 120	defer srv.Close()
 121	defer testutils.CheckSMTPConnLeak(t, srv)
 122	zones := map[string]mockdns.Zone{
 123		"example.invalid.": {
 124			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 125		},
 126		"mx.example.invalid.": {
 127			A: []string{"127.0.0.1"},
 128		},
 129	}
 130
 131	tgt := testTarget(t, zones, nil, nil)
 132	defer tgt.Close()
 133	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
 134
 135	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
 136}
 137
 138func TestRemoteDelivery_NoMXFallback(t *testing.T) {
 139	tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort)
 140	defer tarpit.Close()
 141
 142	zones := map[string]mockdns.Zone{
 143		"example.invalid.": {
 144			MX: []net.MX{},
 145		},
 146	}
 147
 148	tgt := testTarget(t, zones, nil, nil)
 149	defer tgt.Close()
 150
 151	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 152	if err != nil {
 153		t.Fatal(err)
 154	}
 155
 156	if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err == nil {
 157		t.Fatal("Expected an error, got none")
 158	}
 159
 160	if err := delivery.Abort(context.Background()); err != nil {
 161		t.Fatal(err)
 162	}
 163}
 164
 165func TestRemoteDelivery_EmptySender(t *testing.T) {
 166	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 167	defer srv.Close()
 168	defer testutils.CheckSMTPConnLeak(t, srv)
 169	zones := map[string]mockdns.Zone{
 170		"example.invalid.": {
 171			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 172		},
 173		"mx.example.invalid.": {
 174			A: []string{"127.0.0.1"},
 175		},
 176	}
 177
 178	tgt := testTarget(t, zones, nil, nil)
 179	defer tgt.Close()
 180	testutils.DoTestDelivery(t, tgt, "", []string{"test@example.invalid"})
 181
 182	be.CheckMsg(t, 0, "", []string{"test@example.invalid"})
 183}
 184
 185func TestRemoteDelivery_IPLiteral(t *testing.T) {
 186	t.Skip("Support disabled")
 187
 188	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 189	defer srv.Close()
 190	defer testutils.CheckSMTPConnLeak(t, srv)
 191
 192	zones := map[string]mockdns.Zone{
 193		"example.invalid.": {
 194			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 195		},
 196		"mx.example.invalid.": {
 197			A: []string{"127.0.0.1"},
 198		},
 199		"1.0.0.127.in-addr.arpa.": {
 200			PTR: []string{"mx.example.invalid."},
 201		},
 202	}
 203
 204	tgt := testTarget(t, zones, nil, nil)
 205	defer tgt.Close()
 206	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@[127.0.0.1]"})
 207
 208	be.CheckMsg(t, 0, "test@example.com", []string{"test@[127.0.0.1]"})
 209}
 210
 211func TestRemoteDelivery_FallbackMX(t *testing.T) {
 212	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 213	defer srv.Close()
 214	defer testutils.CheckSMTPConnLeak(t, srv)
 215	zones := map[string]mockdns.Zone{
 216		"example.invalid.": {
 217			A: []string{"127.0.0.1"},
 218		},
 219	}
 220
 221	tgt := testTarget(t, zones, nil, nil)
 222	defer tgt.Close()
 223
 224	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
 225	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
 226}
 227
 228func TestRemoteDelivery_BodyNonAtomic(t *testing.T) {
 229	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 230	defer srv.Close()
 231	defer testutils.CheckSMTPConnLeak(t, srv)
 232	zones := map[string]mockdns.Zone{
 233		"example.invalid.": {
 234			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 235		},
 236		"mx.example.invalid.": {
 237			A: []string{"127.0.0.1"},
 238		},
 239	}
 240
 241	tgt := testTarget(t, zones, nil, nil)
 242	defer tgt.Close()
 243
 244	c := multipleErrs{
 245		errs: map[string]error{},
 246	}
 247	testutils.DoTestDeliveryNonAtomic(t, &c, tgt, "test@example.com", []string{"test@example.invalid"})
 248
 249	if err := c.errs["test@example.invalid"]; err != nil {
 250		t.Fatal(err)
 251	}
 252
 253	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
 254}
 255
 256func TestRemoteDelivery_Abort(t *testing.T) {
 257	_, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 258	defer srv.Close()
 259	defer testutils.CheckSMTPConnLeak(t, srv)
 260	zones := map[string]mockdns.Zone{
 261		"example.invalid.": {
 262			MX: []net.MX{{Host: "mx.example.invalid", Pref: 10}},
 263		},
 264		"mx.example.invalid.": {
 265			A: []string{"127.0.0.1"},
 266		},
 267	}
 268
 269	tgt := testTarget(t, zones, nil, nil)
 270	defer tgt.Close()
 271
 272	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 273	if err != nil {
 274		t.Fatal(err)
 275	}
 276
 277	if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err != nil {
 278		t.Fatal(err)
 279	}
 280
 281	if err := delivery.Abort(context.Background()); err != nil {
 282		t.Fatal(err)
 283	}
 284}
 285
 286func TestRemoteDelivery_CommitWithoutBody(t *testing.T) {
 287	_, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 288	defer srv.Close()
 289	defer testutils.CheckSMTPConnLeak(t, srv)
 290	zones := map[string]mockdns.Zone{
 291		"example.invalid.": {
 292			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 293		},
 294		"mx.example.invalid.": {
 295			A: []string{"127.0.0.1"},
 296		},
 297	}
 298
 299	tgt := testTarget(t, zones, nil, nil)
 300	defer tgt.Close()
 301
 302	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 303	if err != nil {
 304		t.Fatal(err)
 305	}
 306
 307	if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err != nil {
 308		t.Fatal(err)
 309	}
 310
 311	// Currently it does nothing, probably it should fail.
 312	if err := delivery.Commit(context.Background()); err != nil {
 313		t.Fatal(err)
 314	}
 315}
 316
 317func TestRemoteDelivery_MAILFROMErr(t *testing.T) {
 318	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 319	defer srv.Close()
 320	defer testutils.CheckSMTPConnLeak(t, srv)
 321	zones := map[string]mockdns.Zone{
 322		"example.invalid.": {
 323			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 324		},
 325		"mx.example.invalid.": {
 326			A: []string{"127.0.0.1"},
 327		},
 328	}
 329
 330	be.MailErr = &smtp.SMTPError{
 331		Code:         550,
 332		EnhancedCode: smtp.EnhancedCode{5, 1, 2},
 333		Message:      "Hey",
 334	}
 335
 336	tgt := testTarget(t, zones, nil, nil)
 337	defer tgt.Close()
 338
 339	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 340	if err != nil {
 341		t.Fatal(err)
 342	}
 343
 344	err = delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{})
 345	testutils.CheckSMTPErr(t, err, 550, exterrors.EnhancedCode{5, 1, 2}, "mx.example.invalid. said: Hey")
 346
 347	if err := delivery.Abort(context.Background()); err != nil {
 348		t.Fatal(err)
 349	}
 350}
 351
 352func TestRemoteDelivery_NoMX(t *testing.T) {
 353	tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort)
 354	defer tarpit.Close()
 355
 356	zones := map[string]mockdns.Zone{
 357		"example.invalid.": {
 358			MX: []net.MX{},
 359		},
 360	}
 361
 362	tgt := testTarget(t, zones, nil, nil)
 363	defer tgt.Close()
 364
 365	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 366	if err != nil {
 367		t.Fatal(err)
 368	}
 369
 370	if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err == nil {
 371		t.Fatal("Expected an error, got none")
 372	}
 373
 374	if err := delivery.Abort(context.Background()); err != nil {
 375		t.Fatal(err)
 376	}
 377}
 378
 379func TestRemoteDelivery_NullMX(t *testing.T) {
 380	// Hang the test if it actually connects to the server to
 381	// deliver the message. Use of testutils.SMTPServer here
 382	// causes weird race conditions.
 383	tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort)
 384	defer tarpit.Close()
 385
 386	zones := map[string]mockdns.Zone{
 387		"example.invalid.": {
 388			MX: []net.MX{{Host: ".", Pref: 10}},
 389		},
 390	}
 391
 392	tgt := testTarget(t, zones, nil, nil)
 393	defer tgt.Close()
 394
 395	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 396	if err != nil {
 397		t.Fatal(err)
 398	}
 399
 400	err = delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{})
 401	testutils.CheckSMTPErr(t, err, 556, exterrors.EnhancedCode{5, 1, 10}, "Domain does not accept email (null MX)")
 402
 403	if err := delivery.Abort(context.Background()); err != nil {
 404		t.Fatal(err)
 405	}
 406}
 407
 408func TestRemoteDelivery_Quarantined(t *testing.T) {
 409	_, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 410	defer srv.Close()
 411	defer testutils.CheckSMTPConnLeak(t, srv)
 412	zones := map[string]mockdns.Zone{
 413		"example.invalid.": {
 414			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 415		},
 416		"mx.example.invalid.": {
 417			A: []string{"127.0.0.1"},
 418		},
 419	}
 420
 421	tgt := testTarget(t, zones, nil, nil)
 422	defer tgt.Close()
 423
 424	meta := module.MsgMetadata{ID: "test..."}
 425
 426	delivery, err := tgt.Start(context.Background(), &meta, "test@example.com")
 427	if err != nil {
 428		t.Fatal(err)
 429	}
 430
 431	if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err != nil {
 432		t.Fatal(err)
 433	}
 434
 435	meta.Quarantine = true
 436
 437	hdr := textproto.Header{}
 438	hdr.Add("B", "2")
 439	hdr.Add("A", "1")
 440	body := buffer.MemoryBuffer{Slice: []byte("foobar\n")}
 441	if err := delivery.Body(context.Background(), textproto.Header{}, body); err == nil {
 442		t.Fatal("Expected an error, got none")
 443	}
 444
 445	if err := delivery.Abort(context.Background()); err != nil {
 446		t.Fatal(err)
 447	}
 448}
 449
 450func TestRemoteDelivery_MAILFROMErr_Repeated(t *testing.T) {
 451	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 452	defer srv.Close()
 453	defer testutils.CheckSMTPConnLeak(t, srv)
 454	zones := map[string]mockdns.Zone{
 455		"example.invalid.": {
 456			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 457		},
 458		"mx.example.invalid.": {
 459			A: []string{"127.0.0.1"},
 460		},
 461	}
 462
 463	be.MailErr = &smtp.SMTPError{
 464		Code:         550,
 465		EnhancedCode: smtp.EnhancedCode{5, 1, 2},
 466		Message:      "Hey",
 467	}
 468
 469	tgt := testTarget(t, zones, nil, nil)
 470	defer tgt.Close()
 471
 472	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 473	if err != nil {
 474		t.Fatal(err)
 475	}
 476
 477	err = delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{})
 478	testutils.CheckSMTPErr(t, err, 550, exterrors.EnhancedCode{5, 1, 2}, "mx.example.invalid. said: Hey")
 479
 480	err = delivery.AddRcpt(context.Background(), "test2@example.invalid", smtp.RcptOptions{})
 481	testutils.CheckSMTPErr(t, err, 550, exterrors.EnhancedCode{5, 1, 2}, "mx.example.invalid. said: Hey")
 482
 483	if err := delivery.Abort(context.Background()); err != nil {
 484		t.Fatal(err)
 485	}
 486}
 487
 488func TestRemoteDelivery_RcptErr(t *testing.T) {
 489	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 490	defer srv.Close()
 491	defer testutils.CheckSMTPConnLeak(t, srv)
 492	zones := map[string]mockdns.Zone{
 493		"example.invalid.": {
 494			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 495		},
 496		"mx.example.invalid.": {
 497			A: []string{"127.0.0.1"},
 498		},
 499	}
 500
 501	be.RcptErr = map[string]error{
 502		"test@example.invalid": &smtp.SMTPError{
 503			Code:         550,
 504			EnhancedCode: smtp.EnhancedCode{5, 1, 2},
 505			Message:      "Hey",
 506		},
 507	}
 508
 509	tgt := testTarget(t, zones, nil, nil)
 510	defer tgt.Close()
 511
 512	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 513	if err != nil {
 514		t.Fatal(err)
 515	}
 516
 517	err = delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{})
 518	testutils.CheckSMTPErr(t, err, 550, exterrors.EnhancedCode{5, 1, 2}, "mx.example.invalid. said: Hey")
 519
 520	// It should be possible to, however, add another recipient and continue
 521	// delivery as if nothing happened.
 522	if err := delivery.AddRcpt(context.Background(), "test2@example.invalid", smtp.RcptOptions{}); err != nil {
 523		t.Fatal(err)
 524	}
 525
 526	hdr := textproto.Header{}
 527	hdr.Add("B", "2")
 528	hdr.Add("A", "1")
 529	body := buffer.MemoryBuffer{Slice: []byte("foobar\n")}
 530	if err := delivery.Body(context.Background(), hdr, body); err != nil {
 531		t.Fatal(err)
 532	}
 533
 534	if err := delivery.Commit(context.Background()); err != nil {
 535		t.Fatal(err)
 536	}
 537
 538	be.CheckMsg(t, 0, "test@example.com", []string{"test2@example.invalid"})
 539}
 540
 541func TestRemoteDelivery_DownMX(t *testing.T) {
 542	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 543	defer srv.Close()
 544	defer testutils.CheckSMTPConnLeak(t, srv)
 545	zones := map[string]mockdns.Zone{
 546		"example.invalid.": {
 547			MX: []net.MX{
 548				{Host: "mx1.example.invalid.", Pref: 20},
 549				{Host: "mx2.example.invalid.", Pref: 10},
 550			},
 551		},
 552		"mx1.example.invalid.": {
 553			A: []string{"127.0.0.1"},
 554		},
 555		"mx2.example.invalid.": {
 556			A: []string{"127.0.0.2"},
 557		},
 558	}
 559
 560	tgt := testTarget(t, zones, nil, nil)
 561	defer tgt.Close()
 562
 563	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
 564	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
 565}
 566
 567func TestRemoteDelivery_AllMXDown(t *testing.T) {
 568	zones := map[string]mockdns.Zone{
 569		"example.invalid.": {
 570			MX: []net.MX{
 571				{Host: "mx1.example.invalid.", Pref: 20},
 572				{Host: "mx2.example.invalid.", Pref: 10},
 573			},
 574		},
 575		"mx1.example.invalid.": {
 576			A: []string{"127.0.0.1"},
 577		},
 578		"mx2.example.invalid.": {
 579			A: []string{"127.0.0.2"},
 580		},
 581	}
 582
 583	tgt := testTarget(t, zones, nil, nil)
 584	defer tgt.Close()
 585
 586	_, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"})
 587	if err == nil {
 588		t.Fatal("Expected an error, got none")
 589	}
 590}
 591
 592func TestRemoteDelivery_Split(t *testing.T) {
 593	be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 594	defer srv1.Close()
 595	defer testutils.CheckSMTPConnLeak(t, srv1)
 596	be2, srv2 := testutils.SMTPServer(t, "127.0.0.2:"+smtpPort)
 597	defer srv2.Close()
 598	defer testutils.CheckSMTPConnLeak(t, srv2)
 599	zones := map[string]mockdns.Zone{
 600		"example.invalid.": {
 601			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 602		},
 603		"example2.invalid.": {
 604			MX: []net.MX{{Host: "mx.example2.invalid.", Pref: 10}},
 605		},
 606		"mx.example.invalid.": {
 607			A: []string{"127.0.0.1"},
 608		},
 609		"mx.example2.invalid.": {
 610			A: []string{"127.0.0.2"},
 611		},
 612	}
 613
 614	tgt := testTarget(t, zones, nil, nil)
 615	defer tgt.Close()
 616
 617	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid", "test@example2.invalid"})
 618
 619	be1.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
 620	be2.CheckMsg(t, 0, "test@example.com", []string{"test@example2.invalid"})
 621}
 622
 623func TestRemoteDelivery_Split_Fail(t *testing.T) {
 624	be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 625	defer srv1.Close()
 626	defer testutils.CheckSMTPConnLeak(t, srv1)
 627	be2, srv2 := testutils.SMTPServer(t, "127.0.0.2:"+smtpPort)
 628	defer srv2.Close()
 629	defer testutils.CheckSMTPConnLeak(t, srv2)
 630	zones := map[string]mockdns.Zone{
 631		"example.invalid.": {
 632			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 633		},
 634		"example2.invalid.": {
 635			MX: []net.MX{{Host: "mx.example2.invalid.", Pref: 10}},
 636		},
 637		"mx.example.invalid.": {
 638			A: []string{"127.0.0.1"},
 639		},
 640		"mx.example2.invalid.": {
 641			A: []string{"127.0.0.2"},
 642		},
 643	}
 644
 645	be1.RcptErr = map[string]error{
 646		"test@example.invalid": &smtp.SMTPError{
 647			Code:         550,
 648			EnhancedCode: smtp.EnhancedCode{5, 1, 2},
 649			Message:      "Hey",
 650		},
 651	}
 652
 653	tgt := testTarget(t, zones, nil, nil)
 654	defer tgt.Close()
 655
 656	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 657	if err != nil {
 658		t.Fatal(err)
 659	}
 660
 661	err = delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{})
 662	if err == nil {
 663		t.Fatal("Expected an error, got none")
 664	}
 665
 666	// It should be possible to, however, add another recipient and continue
 667	// delivery as if nothing happened.
 668	if err := delivery.AddRcpt(context.Background(), "test@example2.invalid", smtp.RcptOptions{}); err != nil {
 669		t.Fatal(err)
 670	}
 671
 672	hdr := textproto.Header{}
 673	hdr.Add("B", "2")
 674	hdr.Add("A", "1")
 675	body := buffer.MemoryBuffer{Slice: []byte("foobar\n")}
 676	if err := delivery.Body(context.Background(), hdr, body); err != nil {
 677		t.Fatal(err)
 678	}
 679
 680	if err := delivery.Commit(context.Background()); err != nil {
 681		t.Fatal(err)
 682	}
 683
 684	be2.CheckMsg(t, 0, "test@example.com", []string{"test@example2.invalid"})
 685}
 686
 687func TestRemoteDelivery_BodyErr(t *testing.T) {
 688	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 689	defer srv.Close()
 690	defer testutils.CheckSMTPConnLeak(t, srv)
 691	zones := map[string]mockdns.Zone{
 692		"example.invalid.": {
 693			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 694		},
 695		"mx.example.invalid.": {
 696			A: []string{"127.0.0.1"},
 697		},
 698	}
 699
 700	be.DataErr = &smtp.SMTPError{
 701		Code:         550,
 702		EnhancedCode: smtp.EnhancedCode{5, 1, 2},
 703		Message:      "Hey",
 704	}
 705
 706	tgt := testTarget(t, zones, nil, nil)
 707	defer tgt.Close()
 708
 709	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 710	if err != nil {
 711		t.Fatal(err)
 712	}
 713
 714	err = delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{})
 715	if err != nil {
 716		t.Fatal(err)
 717	}
 718
 719	hdr := textproto.Header{}
 720	hdr.Add("B", "2")
 721	hdr.Add("A", "1")
 722	body := buffer.MemoryBuffer{Slice: []byte("foobar\n")}
 723	if err := delivery.Body(context.Background(), hdr, body); err == nil {
 724		t.Fatal("expected an error, got none")
 725	}
 726
 727	if err := delivery.Abort(context.Background()); err != nil {
 728		t.Fatal(err)
 729	}
 730}
 731
 732func TestRemoteDelivery_Split_BodyErr(t *testing.T) {
 733	be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 734	defer srv1.Close()
 735	defer testutils.CheckSMTPConnLeak(t, srv1)
 736	_, srv2 := testutils.SMTPServer(t, "127.0.0.2:"+smtpPort)
 737	defer srv2.Close()
 738	defer testutils.CheckSMTPConnLeak(t, srv2)
 739	zones := map[string]mockdns.Zone{
 740		"example.invalid.": {
 741			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 742		},
 743		"example2.invalid.": {
 744			MX: []net.MX{{Host: "mx.example2.invalid.", Pref: 10}},
 745		},
 746		"mx.example.invalid.": {
 747			A: []string{"127.0.0.1"},
 748		},
 749		"mx.example2.invalid.": {
 750			A: []string{"127.0.0.2"},
 751		},
 752	}
 753
 754	be1.DataErr = &smtp.SMTPError{
 755		Code:         421,
 756		EnhancedCode: smtp.EnhancedCode{4, 1, 2},
 757		Message:      "Hey",
 758	}
 759
 760	tgt := testTarget(t, zones, nil, nil)
 761	defer tgt.Close()
 762
 763	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 764	if err != nil {
 765		t.Fatal(err)
 766	}
 767
 768	if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err != nil {
 769		t.Fatal(err)
 770	}
 771	if err := delivery.AddRcpt(context.Background(), "test@example2.invalid", smtp.RcptOptions{}); err != nil {
 772		t.Fatal(err)
 773	}
 774
 775	hdr := textproto.Header{}
 776	hdr.Add("B", "2")
 777	hdr.Add("A", "1")
 778	body := buffer.MemoryBuffer{Slice: []byte("foobar\n")}
 779	err = delivery.Body(context.Background(), hdr, body)
 780	testutils.CheckSMTPErr(t, err, 451, exterrors.EnhancedCode{4, 0, 0},
 781		"Partial delivery failure, additional attempts may result in duplicates")
 782
 783	if err := delivery.Abort(context.Background()); err != nil {
 784		t.Fatal(err)
 785	}
 786}
 787
 788func TestRemoteDelivery_Split_BodyErr_NonAtomic(t *testing.T) {
 789	be1, srv1 := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 790	defer srv1.Close()
 791	defer testutils.CheckSMTPConnLeak(t, srv1)
 792	_, srv2 := testutils.SMTPServer(t, "127.0.0.2:"+smtpPort)
 793	defer srv2.Close()
 794	defer testutils.CheckSMTPConnLeak(t, srv2)
 795	zones := map[string]mockdns.Zone{
 796		"example.invalid.": {
 797			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 798		},
 799		"example2.invalid.": {
 800			MX: []net.MX{{Host: "mx.example2.invalid.", Pref: 10}},
 801		},
 802		"mx.example.invalid.": {
 803			A: []string{"127.0.0.1"},
 804		},
 805		"mx.example2.invalid.": {
 806			A: []string{"127.0.0.2"},
 807		},
 808	}
 809
 810	be1.DataErr = &smtp.SMTPError{
 811		Code:         550,
 812		EnhancedCode: smtp.EnhancedCode{5, 1, 2},
 813		Message:      "Hey",
 814	}
 815
 816	tgt := testTarget(t, zones, nil, nil)
 817	defer tgt.Close()
 818
 819	delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")
 820	if err != nil {
 821		t.Fatal(err)
 822	}
 823
 824	if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err != nil {
 825		t.Fatal(err)
 826	}
 827	if err := delivery.AddRcpt(context.Background(), "test2@example.invalid", smtp.RcptOptions{}); err != nil {
 828		t.Fatal(err)
 829	}
 830	if err := delivery.AddRcpt(context.Background(), "test@example2.invalid", smtp.RcptOptions{}); err != nil {
 831		t.Fatal(err)
 832	}
 833
 834	hdr := textproto.Header{}
 835	hdr.Add("B", "2")
 836	hdr.Add("A", "1")
 837	body := buffer.MemoryBuffer{Slice: []byte("foobar\n")}
 838	c := multipleErrs{
 839		errs: map[string]error{},
 840	}
 841	delivery.(module.PartialDelivery).BodyNonAtomic(context.Background(), &c, hdr, body)
 842
 843	testutils.CheckSMTPErr(t, c.errs["test@example.invalid"],
 844		550, exterrors.EnhancedCode{5, 1, 2}, "mx.example.invalid. said: Hey")
 845	testutils.CheckSMTPErr(t, c.errs["test2@example.invalid"],
 846		550, exterrors.EnhancedCode{5, 1, 2}, "mx.example.invalid. said: Hey")
 847	if err := c.errs["test@example2.invalid"]; err != nil {
 848		t.Errorf("Unexpected error for non-failing connection: %v", err)
 849	}
 850
 851	if err := delivery.Abort(context.Background()); err != nil {
 852		t.Fatal(err)
 853	}
 854}
 855
 856func TestRemoteDelivery_TLSErrFallback(t *testing.T) {
 857	clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort)
 858	defer srv.Close()
 859	defer testutils.CheckSMTPConnLeak(t, srv)
 860	zones := map[string]mockdns.Zone{
 861		"example.invalid.": {
 862			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 863		},
 864		"mx.example.invalid.": {
 865			A: []string{"127.0.0.1"},
 866		},
 867	}
 868
 869	// Cause failure through version incompatibility.
 870	clientCfg.MaxVersion = tls.VersionTLS12
 871	clientCfg.MinVersion = tls.VersionTLS12
 872	srv.TLSConfig.MinVersion = tls.VersionTLS11
 873	srv.TLSConfig.MaxVersion = tls.VersionTLS11
 874
 875	tgt := testTarget(t, zones, nil, nil)
 876	tgt.tlsConfig = clientCfg
 877	defer tgt.Close()
 878
 879	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
 880	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
 881}
 882
 883func TestRemoteDelivery_RequireTLS_Missing(t *testing.T) {
 884	_, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
 885	defer srv.Close()
 886	defer testutils.CheckSMTPConnLeak(t, srv)
 887	zones := map[string]mockdns.Zone{
 888		"example.invalid.": {
 889			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 890		},
 891		"mx.example.invalid.": {
 892			A: []string{"127.0.0.1"},
 893		},
 894	}
 895
 896	tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{
 897		&localPolicy{minTLSLevel: module.TLSEncrypted},
 898	})
 899	defer tgt.Close()
 900
 901	_, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"})
 902	if err == nil {
 903		t.Errorf("expected an error, got none")
 904	}
 905}
 906
 907func TestRemoteDelivery_RequireTLS_Present(t *testing.T) {
 908	clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort)
 909	defer srv.Close()
 910	defer testutils.CheckSMTPConnLeak(t, srv)
 911	zones := map[string]mockdns.Zone{
 912		"example.invalid.": {
 913			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 914		},
 915		"mx.example.invalid.": {
 916			A: []string{"127.0.0.1"},
 917		},
 918	}
 919
 920	tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{
 921		&localPolicy{minTLSLevel: module.TLSEncrypted},
 922	})
 923	tgt.tlsConfig = clientCfg
 924	defer tgt.Close()
 925
 926	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
 927	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
 928}
 929
 930func TestRemoteDelivery_RequireTLS_NoErrFallback(t *testing.T) {
 931	clientCfg, _, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort)
 932	defer srv.Close()
 933	defer testutils.CheckSMTPConnLeak(t, srv)
 934	zones := map[string]mockdns.Zone{
 935		"example.invalid.": {
 936			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 937		},
 938		"mx.example.invalid.": {
 939			A: []string{"127.0.0.1"},
 940		},
 941	}
 942
 943	// Cause failure through version incompatibility.
 944	clientCfg.MaxVersion = tls.VersionTLS12
 945	clientCfg.MinVersion = tls.VersionTLS12
 946	srv.TLSConfig.MinVersion = tls.VersionTLS11
 947	srv.TLSConfig.MaxVersion = tls.VersionTLS11
 948
 949	tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{
 950		&localPolicy{minTLSLevel: module.TLSEncrypted},
 951	})
 952	tgt.tlsConfig = clientCfg
 953	defer tgt.Close()
 954
 955	_, err := testutils.DoTestDeliveryErr(t, tgt, "test@example.com", []string{"test@example.invalid"})
 956	if err == nil {
 957		t.Fatal("Expected an error, got none")
 958	}
 959}
 960
 961func TestRemoteDelivery_TLS_FallbackNoVerify(t *testing.T) {
 962	_, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort)
 963	defer srv.Close()
 964	defer testutils.CheckSMTPConnLeak(t, srv)
 965	zones := map[string]mockdns.Zone{
 966		"example.invalid.": {
 967			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 968		},
 969		"mx.example.invalid.": {
 970			A: []string{"127.0.0.1"},
 971		},
 972	}
 973
 974	// tlsConfig is not configured to trust server cert.
 975	tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{
 976		&localPolicy{minTLSLevel: module.TLSEncrypted},
 977	})
 978	defer tgt.Close()
 979
 980	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
 981	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
 982
 983	// But it should still be delivered over TLS.
 984	tlsState, ok := be.Messages[0].Conn.TLSConnectionState()
 985	if !ok || !tlsState.HandshakeComplete {
 986		t.Fatal("Message was not delivered over TLS")
 987	}
 988}
 989
 990func TestRemoteDelivery_TLS_FallbackPlaintext(t *testing.T) {
 991	clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+smtpPort)
 992	defer srv.Close()
 993	defer testutils.CheckSMTPConnLeak(t, srv)
 994	zones := map[string]mockdns.Zone{
 995		"example.invalid.": {
 996			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
 997		},
 998		"mx.example.invalid.": {
 999			A: []string{"127.0.0.1"},
1000		},
1001	}
1002
1003	// Cause failure through version incompatibility.
1004	clientCfg.MaxVersion = tls.VersionTLS12
1005	clientCfg.MinVersion = tls.VersionTLS12
1006	srv.TLSConfig.MinVersion = tls.VersionTLS11
1007	srv.TLSConfig.MaxVersion = tls.VersionTLS11
1008
1009	tgt := testTarget(t, zones, nil, nil)
1010	tgt.tlsConfig = clientCfg
1011	defer tgt.Close()
1012
1013	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
1014	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
1015}
1016
1017func TestMain(m *testing.M) {
1018	remoteSmtpPort := flag.String("test.smtpport", "random", "(maddy) SMTP port to use for connections in tests")
1019	flag.Parse()
1020
1021	if *remoteSmtpPort == "random" {
1022		*remoteSmtpPort = strconv.Itoa(rand.Intn(65536-10000) + 10000)
1023	}
1024
1025	smtpPort = *remoteSmtpPort
1026	os.Exit(m.Run())
1027}
1028
1029func TestRemoteDelivery_ConnReuse(t *testing.T) {
1030	be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)
1031	defer srv.Close()
1032	defer testutils.CheckSMTPConnLeak(t, srv)
1033	zones := map[string]mockdns.Zone{
1034		"example.invalid.": {
1035			MX: []net.MX{{Host: "mx.example.invalid.", Pref: 10}},
1036		},
1037		"mx.example.invalid.": {
1038			A: []string{"127.0.0.1"},
1039		},
1040	}
1041
1042	tgt := testTarget(t, zones, nil, nil)
1043	tgt.connReuseLimit = 5
1044	defer tgt.Close()
1045	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
1046	testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})
1047
1048	be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})
1049	be.CheckMsg(t, 1, "test@example.com", []string{"test@example.invalid"})
1050
1051	if len(be.SourceEndpoints) != 1 {
1052		t.Fatal("Only one session should be used, found", be.SourceEndpoints)
1053	}
1054}