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 smtp_downstream2021import (22 "errors"23 "flag"24 "math/rand"25 "os"26 "strconv"27 "testing"2829 "github.com/emersion/go-smtp"30 "github.com/foxcpp/maddy/framework/config"31 "github.com/foxcpp/maddy/framework/exterrors"32 "github.com/foxcpp/maddy/internal/testutils"33)3435var testPort string3637func TestDownstreamDelivery(t *testing.T) {38 be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort)39 defer srv.Close()40 defer testutils.CheckSMTPConnLeak(t, srv)4142 tarpit := testutils.FailOnConn(t, "127.0.0.2:"+testPort)43 defer tarpit.Close()4445 mod := &Downstream{46 hostname: "mx.example.invalid",47 endpoints: []config.Endpoint{48 {49 Scheme: "tcp",50 Host: "127.0.0.1",51 Port: testPort,52 },53 {54 Scheme: "tcp",55 Host: "127.0.0.2",56 Port: testPort,57 },58 },59 log: testutils.Logger(t, "target.smtp"),60 }6162 testutils.DoTestDelivery(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"})63 be.CheckMsg(t, 0, "test@example.invalid", []string{"rcpt@example.invalid"})64}6566func TestDownstreamDelivery_LMTP(t *testing.T) {67 be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort, func(srv *smtp.Server) {68 srv.LMTP = true69 })70 be.LMTPDataErr = []error{71 nil,72 &smtp.SMTPError{73 Code: 501,74 Message: "nop",75 },76 }77 defer srv.Close()78 defer testutils.CheckSMTPConnLeak(t, srv)7980 mod := &Downstream{81 hostname: "mx.example.invalid",82 endpoints: []config.Endpoint{83 {84 Scheme: "tcp",85 Host: "127.0.0.1",86 Port: testPort,87 },88 },89 modName: "target.lmtp",90 lmtp: true,91 log: testutils.Logger(t, "lmtp_downstream"),92 }9394 sc := make(statusCollector)9596 testutils.DoTestDeliveryNonAtomic(t, &sc, mod, "test@example.invalid", []string{"rcpt1@example.invalid", "rcpt2@example.invalid"})97 be.CheckMsg(t, 0, "test@example.invalid", []string{"rcpt1@example.invalid", "rcpt2@example.invalid"})9899 if len(sc) != 2 {100 t.Fatal("Two statuses should be set")101 }102 if err := sc["rcpt1@example.invalid"]; err != nil {103 t.Fatal("Unexpected error for rcpt1:", err)104 }105 if sc["rcpt2@example.invalid"] == nil {106 t.Fatal("Expected an error for rcpt2")107 }108 var rcptErr *exterrors.SMTPError109 if !errors.As(sc["rcpt2@example.invalid"], &rcptErr) {110 t.Fatalf("Not SMTPError: %T", rcptErr)111 }112 if rcptErr.Code != 501 {113 t.Fatal("Wrong SMTP code:", rcptErr.Code)114 }115}116117func TestDownstreamDelivery_LMTP_ErrorCoerce(t *testing.T) {118 be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort, func(srv *smtp.Server) {119 srv.LMTP = true120 })121 be.LMTPDataErr = []error{122 nil,123 &smtp.SMTPError{124 Code: 501,125 Message: "nop",126 },127 }128 defer srv.Close()129 defer testutils.CheckSMTPConnLeak(t, srv)130131 mod := &Downstream{132 hostname: "mx.example.invalid",133 endpoints: []config.Endpoint{134 {135 Scheme: "tcp",136 Host: "127.0.0.1",137 Port: testPort,138 },139 },140 modName: "target.lmtp",141 lmtp: true,142 log: testutils.Logger(t, "lmtp_downstream"),143 }144145 _, err := testutils.DoTestDeliveryErr(t, mod, "test@example.invalid", []string{"rcpt1@example.invalid", "rcpt2@example.invalid"})146 if err == nil {147 t.Error("expected failure")148 }149}150151type statusCollector map[string]error152153func (sc *statusCollector) SetStatus(rcptTo string, err error) {154 (*sc)[rcptTo] = err155}156157func TestDownstreamDelivery_Fallback(t *testing.T) {158 be, srv := testutils.SMTPServer(t, "127.0.0.2:"+testPort)159 defer srv.Close()160 defer testutils.CheckSMTPConnLeak(t, srv)161162 mod := &Downstream{163 hostname: "mx.example.invalid",164 endpoints: []config.Endpoint{165 {166 Scheme: "tcp",167 Host: "127.0.0.1",168 Port: testPort,169 },170 {171 Scheme: "tcp",172 Host: "127.0.0.2",173 Port: testPort,174 },175 },176 log: testutils.Logger(t, "target.smtp"),177 }178179 testutils.DoTestDelivery(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"})180 be.CheckMsg(t, 0, "test@example.invalid", []string{"rcpt@example.invalid"})181}182183func TestDownstreamDelivery_MAILErr(t *testing.T) {184 be, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort)185 defer srv.Close()186 defer testutils.CheckSMTPConnLeak(t, srv)187188 be.MailErr = &smtp.SMTPError{189 Code: 550,190 EnhancedCode: smtp.EnhancedCode{5, 1, 2},191 Message: "Hey",192 }193194 mod := &Downstream{195 hostname: "mx.example.invalid",196 endpoints: []config.Endpoint{197 {198 Scheme: "tcp",199 Host: "127.0.0.1",200 Port: testPort,201 },202 },203 log: testutils.Logger(t, "target.smtp"),204 }205206 _, err := testutils.DoTestDeliveryErr(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"})207 testutils.CheckSMTPErr(t, err, 550, exterrors.EnhancedCode{5, 1, 2}, "Hey")208}209210func TestDownstreamDelivery_StartTLS(t *testing.T) {211 clientCfg, be, srv := testutils.SMTPServerSTARTTLS(t, "127.0.0.1:"+testPort)212 defer srv.Close()213 defer testutils.CheckSMTPConnLeak(t, srv)214215 mod := &Downstream{216 hostname: "mx.example.invalid",217 endpoints: []config.Endpoint{218 {219 Scheme: "tcp",220 Host: "127.0.0.1",221 Port: testPort,222 },223 },224 tlsConfig: clientCfg.Clone(),225 starttls: true,226 log: testutils.Logger(t, "target.smtp"),227 }228229 testutils.DoTestDelivery(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"})230 be.CheckMsg(t, 0, "test@example.invalid", []string{"rcpt@example.invalid"})231232 tlsState, ok := be.Messages[0].Conn.TLSConnectionState()233 if !ok || !tlsState.HandshakeComplete {234 t.Fatal("Message was not delivered over TLS")235 }236}237238func TestDownstreamDelivery_StartTLS_NoFallback(t *testing.T) {239 _, srv := testutils.SMTPServer(t, "127.0.0.1:"+testPort)240 defer srv.Close()241 defer testutils.CheckSMTPConnLeak(t, srv)242243 mod := &Downstream{244 hostname: "mx.example.invalid",245 endpoints: []config.Endpoint{246 {247 Scheme: "tcp",248 Host: "127.0.0.1",249 Port: testPort,250 },251 },252 starttls: true,253 log: testutils.Logger(t, "target.smtp"),254 }255256 _, err := testutils.DoTestDeliveryErr(t, mod, "test@example.invalid", []string{"rcpt@example.invalid"})257 if err == nil {258 t.Error("Expected an error, got none")259 }260}261262func TestMain(m *testing.M) {263 remoteSmtpPort := flag.String("test.smtpport", "random", "(maddy) SMTP port to use for connections in tests")264 flag.Parse()265266 if *remoteSmtpPort == "random" {267 *remoteSmtpPort = strconv.Itoa(rand.Intn(65536-10000) + 10000)268 }269270 testPort = *remoteSmtpPort271 os.Exit(m.Run())272}