1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package remote2021import (22 "context"23 "crypto/tls"24 "flag"25 "math/rand"26 "net"27 "os"28 "strconv"29 "testing"3031 "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)4445// .invalid TLD is used here to make sure if there is something wrong about46// DNS hooks and lookups go to the real Internet, they will not result in47// any useful data that can lead to outgoing connections being made.4849func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver *dns.ExtResolver,50 extraPolicies []module.MXAuthPolicy) *Target {51 resolver := &mockdns.Resolver{Zones: zones}5253 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 cache66 MaxConnLifetimeSec: 150, // 2.5 mins, half of recommended idle time from RFC 532167 StaleKeyLifetimeSec: 60 * 5, // should be bigger than MaxConnLifetimeSec68 }),69 }7071 return &tgt72}7374func 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 }9192 p.mtastsGet = mtastsGet93 p.log = testutils.Logger(t, "remote/mtasts")94 p.cache.Resolver = &mockdns.Resolver{Zones: zones}95 p.StartUpdater()9697 return p98}99100func 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 }112113 p.extResolver = extR114 p.log = testutils.Logger(t, "remote/dane")115 return p116}117118func 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 }130131 tgt := testTarget(t, zones, nil, nil)132 defer tgt.Close()133 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})134135 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})136}137138func TestRemoteDelivery_NoMXFallback(t *testing.T) {139 tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort)140 defer tarpit.Close()141142 zones := map[string]mockdns.Zone{143 "example.invalid.": {144 MX: []net.MX{},145 },146 }147148 tgt := testTarget(t, zones, nil, nil)149 defer tgt.Close()150151 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")152 if err != nil {153 t.Fatal(err)154 }155156 if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err == nil {157 t.Fatal("Expected an error, got none")158 }159160 if err := delivery.Abort(context.Background()); err != nil {161 t.Fatal(err)162 }163}164165func 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 }177178 tgt := testTarget(t, zones, nil, nil)179 defer tgt.Close()180 testutils.DoTestDelivery(t, tgt, "", []string{"test@example.invalid"})181182 be.CheckMsg(t, 0, "", []string{"test@example.invalid"})183}184185func TestRemoteDelivery_IPLiteral(t *testing.T) {186 t.Skip("Support disabled")187188 be, srv := testutils.SMTPServer(t, "127.0.0.1:"+smtpPort)189 defer srv.Close()190 defer testutils.CheckSMTPConnLeak(t, srv)191192 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 }203204 tgt := testTarget(t, zones, nil, nil)205 defer tgt.Close()206 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@[127.0.0.1]"})207208 be.CheckMsg(t, 0, "test@example.com", []string{"test@[127.0.0.1]"})209}210211func 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 }220221 tgt := testTarget(t, zones, nil, nil)222 defer tgt.Close()223224 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})225 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})226}227228func 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 }240241 tgt := testTarget(t, zones, nil, nil)242 defer tgt.Close()243244 c := multipleErrs{245 errs: map[string]error{},246 }247 testutils.DoTestDeliveryNonAtomic(t, &c, tgt, "test@example.com", []string{"test@example.invalid"})248249 if err := c.errs["test@example.invalid"]; err != nil {250 t.Fatal(err)251 }252253 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})254}255256func 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 }268269 tgt := testTarget(t, zones, nil, nil)270 defer tgt.Close()271272 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")273 if err != nil {274 t.Fatal(err)275 }276277 if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err != nil {278 t.Fatal(err)279 }280281 if err := delivery.Abort(context.Background()); err != nil {282 t.Fatal(err)283 }284}285286func 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 }298299 tgt := testTarget(t, zones, nil, nil)300 defer tgt.Close()301302 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")303 if err != nil {304 t.Fatal(err)305 }306307 if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err != nil {308 t.Fatal(err)309 }310311 // Currently it does nothing, probably it should fail.312 if err := delivery.Commit(context.Background()); err != nil {313 t.Fatal(err)314 }315}316317func 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 }329330 be.MailErr = &smtp.SMTPError{331 Code: 550,332 EnhancedCode: smtp.EnhancedCode{5, 1, 2},333 Message: "Hey",334 }335336 tgt := testTarget(t, zones, nil, nil)337 defer tgt.Close()338339 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")340 if err != nil {341 t.Fatal(err)342 }343344 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")346347 if err := delivery.Abort(context.Background()); err != nil {348 t.Fatal(err)349 }350}351352func TestRemoteDelivery_NoMX(t *testing.T) {353 tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort)354 defer tarpit.Close()355356 zones := map[string]mockdns.Zone{357 "example.invalid.": {358 MX: []net.MX{},359 },360 }361362 tgt := testTarget(t, zones, nil, nil)363 defer tgt.Close()364365 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")366 if err != nil {367 t.Fatal(err)368 }369370 if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err == nil {371 t.Fatal("Expected an error, got none")372 }373374 if err := delivery.Abort(context.Background()); err != nil {375 t.Fatal(err)376 }377}378379func TestRemoteDelivery_NullMX(t *testing.T) {380 // Hang the test if it actually connects to the server to381 // deliver the message. Use of testutils.SMTPServer here382 // causes weird race conditions.383 tarpit := testutils.FailOnConn(t, "127.0.0.1:"+smtpPort)384 defer tarpit.Close()385386 zones := map[string]mockdns.Zone{387 "example.invalid.": {388 MX: []net.MX{{Host: ".", Pref: 10}},389 },390 }391392 tgt := testTarget(t, zones, nil, nil)393 defer tgt.Close()394395 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")396 if err != nil {397 t.Fatal(err)398 }399400 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)")402403 if err := delivery.Abort(context.Background()); err != nil {404 t.Fatal(err)405 }406}407408func 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 }420421 tgt := testTarget(t, zones, nil, nil)422 defer tgt.Close()423424 meta := module.MsgMetadata{ID: "test..."}425426 delivery, err := tgt.Start(context.Background(), &meta, "test@example.com")427 if err != nil {428 t.Fatal(err)429 }430431 if err := delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{}); err != nil {432 t.Fatal(err)433 }434435 meta.Quarantine = true436437 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 }444445 if err := delivery.Abort(context.Background()); err != nil {446 t.Fatal(err)447 }448}449450func 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 }462463 be.MailErr = &smtp.SMTPError{464 Code: 550,465 EnhancedCode: smtp.EnhancedCode{5, 1, 2},466 Message: "Hey",467 }468469 tgt := testTarget(t, zones, nil, nil)470 defer tgt.Close()471472 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")473 if err != nil {474 t.Fatal(err)475 }476477 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")479480 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")482483 if err := delivery.Abort(context.Background()); err != nil {484 t.Fatal(err)485 }486}487488func 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 }500501 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 }508509 tgt := testTarget(t, zones, nil, nil)510 defer tgt.Close()511512 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")513 if err != nil {514 t.Fatal(err)515 }516517 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")519520 // It should be possible to, however, add another recipient and continue521 // delivery as if nothing happened.522 if err := delivery.AddRcpt(context.Background(), "test2@example.invalid", smtp.RcptOptions{}); err != nil {523 t.Fatal(err)524 }525526 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 }533534 if err := delivery.Commit(context.Background()); err != nil {535 t.Fatal(err)536 }537538 be.CheckMsg(t, 0, "test@example.com", []string{"test2@example.invalid"})539}540541func 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 }559560 tgt := testTarget(t, zones, nil, nil)561 defer tgt.Close()562563 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})564 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})565}566567func 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 }582583 tgt := testTarget(t, zones, nil, nil)584 defer tgt.Close()585586 _, 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}591592func 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 }613614 tgt := testTarget(t, zones, nil, nil)615 defer tgt.Close()616617 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid", "test@example2.invalid"})618619 be1.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})620 be2.CheckMsg(t, 0, "test@example.com", []string{"test@example2.invalid"})621}622623func 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 }644645 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 }652653 tgt := testTarget(t, zones, nil, nil)654 defer tgt.Close()655656 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")657 if err != nil {658 t.Fatal(err)659 }660661 err = delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{})662 if err == nil {663 t.Fatal("Expected an error, got none")664 }665666 // It should be possible to, however, add another recipient and continue667 // delivery as if nothing happened.668 if err := delivery.AddRcpt(context.Background(), "test@example2.invalid", smtp.RcptOptions{}); err != nil {669 t.Fatal(err)670 }671672 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 }679680 if err := delivery.Commit(context.Background()); err != nil {681 t.Fatal(err)682 }683684 be2.CheckMsg(t, 0, "test@example.com", []string{"test@example2.invalid"})685}686687func 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 }699700 be.DataErr = &smtp.SMTPError{701 Code: 550,702 EnhancedCode: smtp.EnhancedCode{5, 1, 2},703 Message: "Hey",704 }705706 tgt := testTarget(t, zones, nil, nil)707 defer tgt.Close()708709 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")710 if err != nil {711 t.Fatal(err)712 }713714 err = delivery.AddRcpt(context.Background(), "test@example.invalid", smtp.RcptOptions{})715 if err != nil {716 t.Fatal(err)717 }718719 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 }726727 if err := delivery.Abort(context.Background()); err != nil {728 t.Fatal(err)729 }730}731732func 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 }753754 be1.DataErr = &smtp.SMTPError{755 Code: 421,756 EnhancedCode: smtp.EnhancedCode{4, 1, 2},757 Message: "Hey",758 }759760 tgt := testTarget(t, zones, nil, nil)761 defer tgt.Close()762763 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")764 if err != nil {765 t.Fatal(err)766 }767768 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 }774775 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")782783 if err := delivery.Abort(context.Background()); err != nil {784 t.Fatal(err)785 }786}787788func 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 }809810 be1.DataErr = &smtp.SMTPError{811 Code: 550,812 EnhancedCode: smtp.EnhancedCode{5, 1, 2},813 Message: "Hey",814 }815816 tgt := testTarget(t, zones, nil, nil)817 defer tgt.Close()818819 delivery, err := tgt.Start(context.Background(), &module.MsgMetadata{ID: "test..."}, "test@example.com")820 if err != nil {821 t.Fatal(err)822 }823824 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 }833834 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)842843 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 }850851 if err := delivery.Abort(context.Background()); err != nil {852 t.Fatal(err)853 }854}855856func 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 }868869 // Cause failure through version incompatibility.870 clientCfg.MaxVersion = tls.VersionTLS12871 clientCfg.MinVersion = tls.VersionTLS12872 srv.TLSConfig.MinVersion = tls.VersionTLS11873 srv.TLSConfig.MaxVersion = tls.VersionTLS11874875 tgt := testTarget(t, zones, nil, nil)876 tgt.tlsConfig = clientCfg877 defer tgt.Close()878879 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})880 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})881}882883func 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 }895896 tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{897 &localPolicy{minTLSLevel: module.TLSEncrypted},898 })899 defer tgt.Close()900901 _, 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}906907func 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 }919920 tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{921 &localPolicy{minTLSLevel: module.TLSEncrypted},922 })923 tgt.tlsConfig = clientCfg924 defer tgt.Close()925926 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})927 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})928}929930func 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 }942943 // Cause failure through version incompatibility.944 clientCfg.MaxVersion = tls.VersionTLS12945 clientCfg.MinVersion = tls.VersionTLS12946 srv.TLSConfig.MinVersion = tls.VersionTLS11947 srv.TLSConfig.MaxVersion = tls.VersionTLS11948949 tgt := testTarget(t, zones, nil, []module.MXAuthPolicy{950 &localPolicy{minTLSLevel: module.TLSEncrypted},951 })952 tgt.tlsConfig = clientCfg953 defer tgt.Close()954955 _, 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}960961func 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 }973974 // 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()979980 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})981 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})982983 // 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}989990func 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 }10021003 // Cause failure through version incompatibility.1004 clientCfg.MaxVersion = tls.VersionTLS121005 clientCfg.MinVersion = tls.VersionTLS121006 srv.TLSConfig.MinVersion = tls.VersionTLS111007 srv.TLSConfig.MaxVersion = tls.VersionTLS1110081009 tgt := testTarget(t, zones, nil, nil)1010 tgt.tlsConfig = clientCfg1011 defer tgt.Close()10121013 testutils.DoTestDelivery(t, tgt, "test@example.com", []string{"test@example.invalid"})1014 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})1015}10161017func TestMain(m *testing.M) {1018 remoteSmtpPort := flag.String("test.smtpport", "random", "(maddy) SMTP port to use for connections in tests")1019 flag.Parse()10201021 if *remoteSmtpPort == "random" {1022 *remoteSmtpPort = strconv.Itoa(rand.Intn(65536-10000) + 10000)1023 }10241025 smtpPort = *remoteSmtpPort1026 os.Exit(m.Run())1027}10281029func 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 }10411042 tgt := testTarget(t, zones, nil, nil)1043 tgt.connReuseLimit = 51044 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"})10471048 be.CheckMsg(t, 0, "test@example.com", []string{"test@example.invalid"})1049 be.CheckMsg(t, 1, "test@example.com", []string{"test@example.invalid"})10501051 if len(be.SourceEndpoints) != 1 {1052 t.Fatal("Only one session should be used, found", be.SourceEndpoints)1053 }1054}