Move smtp.RelayMessage into a new smtp.MTA interface.
authorRobert Sesek <rsesek@bluestatic.org>
Sun, 29 May 2022 17:09:56 +0000 (13:09 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Sun, 29 May 2022 17:09:56 +0000 (13:09 -0400)
smtp.go
smtp/relay.go
smtp/relay_test.go
smtp/server.go

diff --git a/smtp.go b/smtp.go
index 90f0ec535a01a9f7cdf9ceb206f8f05017e0238e..5b6952d8a6928597f4615da320bbd04f34557961 100644 (file)
--- a/smtp.go
+++ b/smtp.go
@@ -25,6 +25,7 @@ func runSMTPServer(config Config, log *zap.Logger) <-chan ServerControlMessage {
                controlChan: make(chan ServerControlMessage),
                log:         log.With(zap.String("server", "smtp")),
        }
+       server.mta = smtp.NewDefaultMTA(&server, server.log)
        go server.run()
        return server.controlChan
 }
@@ -33,6 +34,8 @@ type smtpServer struct {
        config    Config
        tlsConfig *tls.Config
 
+       mta smtp.MTA
+
        log *zap.Logger
 
        controlChan chan ServerControlMessage
@@ -144,8 +147,7 @@ func (server *smtpServer) DeliverMessage(en smtp.Envelope) *smtp.ReplyLine {
 }
 
 func (server *smtpServer) RelayMessage(en smtp.Envelope) {
-       log := server.log.With(zap.String("id", en.ID))
-       go smtp.RelayMessage(server, en, log)
+       go server.mta.RelayMessage(en)
 }
 
 func (server *smtpServer) maildropForAddress(addr mail.Address) string {
index 5d65d84d415a83af635abbe80e438a286ebe5cb7..ac637f354104687e657706274e09cdea09b2b5aa 100644 (file)
@@ -20,21 +20,21 @@ import (
        "go.uber.org/zap"
 )
 
-func RelayMessage(server Server, env Envelope, log *zap.Logger) {
+func (m *mta) RelayMessage(env Envelope) {
        for _, rcptTo := range env.RcptTo {
-               sendLog := log.With(zap.String("address", rcptTo.Address))
+               sendLog := m.log.With(zap.String("address", rcptTo.Address), zap.String("id", env.ID))
 
                domain := DomainForAddress(rcptTo)
                mx, err := net.LookupMX(domain)
                if err != nil || len(mx) < 1 {
-                       deliverRelayFailure(server, env, log, rcptTo.Address, "failed to lookup MX records", err)
+                       m.deliverRelayFailure(env, sendLog, rcptTo.Address, "failed to lookup MX records", err)
                        return
                }
-               relayMessageToHost(server, env, sendLog, rcptTo.Address, mx[0].Host, "25")
+               m.relayMessageToHost(env, sendLog, rcptTo.Address, mx[0].Host, "25")
        }
 }
 
-func relayMessageToHost(server Server, env Envelope, log *zap.Logger, to, host, port string) {
+func (m *mta) relayMessageToHost(env Envelope, log *zap.Logger, to, host, port string) {
        from := env.MailFrom.Address
        hostPort := net.JoinHostPort(host, port)
        log = log.With(zap.String("host", hostPort))
@@ -42,49 +42,49 @@ func relayMessageToHost(server Server, env Envelope, log *zap.Logger, to, host,
        c, err := smtp.Dial(hostPort)
        if err != nil {
                // TODO - retry, or look at other MX records
-               deliverRelayFailure(server, env, log, to, "failed to dial host", err)
+               m.deliverRelayFailure(env, log, to, "failed to dial host", err)
                return
        }
        defer c.Quit()
 
-       if err = c.Hello(server.Name()); err != nil {
-               deliverRelayFailure(server, env, log, to, "failed to HELO", err)
+       if err = c.Hello(m.server.Name()); err != nil {
+               m.deliverRelayFailure(env, log, to, "failed to HELO", err)
                return
        }
 
        if hasTls, _ := c.Extension("STARTTLS"); hasTls {
                config := &tls.Config{ServerName: host}
                if err = c.StartTLS(config); err != nil {
-                       deliverRelayFailure(server, env, log, to, "failed to STARTTLS", err)
+                       m.deliverRelayFailure(env, log, to, "failed to STARTTLS", err)
                        return
                }
        }
 
        if err = c.Mail(from); err != nil {
-               deliverRelayFailure(server, env, log, to, "failed MAIL FROM", err)
+               m.deliverRelayFailure(env, log, to, "failed MAIL FROM", err)
                return
        }
 
        if err = c.Rcpt(to); err != nil {
-               deliverRelayFailure(server, env, log, to, "failed to RCPT TO", err)
+               m.deliverRelayFailure(env, log, to, "failed to RCPT TO", err)
                return
        }
 
        wc, err := c.Data()
        if err != nil {
-               deliverRelayFailure(server, env, log, to, "failed to DATA", err)
+               m.deliverRelayFailure(env, log, to, "failed to DATA", err)
                return
        }
 
        _, err = wc.Write(env.Data)
        if err != nil {
                wc.Close()
-               deliverRelayFailure(server, env, log, to, "failed to write DATA", err)
+               m.deliverRelayFailure(env, log, to, "failed to write DATA", err)
                return
        }
 
        if err = wc.Close(); err != nil {
-               deliverRelayFailure(server, env, log, to, "failed to close DATA", err)
+               m.deliverRelayFailure(env, log, to, "failed to close DATA", err)
                return
        }
 }
@@ -93,7 +93,7 @@ func relayMessageToHost(server Server, env Envelope, log *zap.Logger, to, host,
 // writes to |log| the |errorStr| and |sendErr|, as well as preparing a new
 // message, based of |env|, delivered to |server| that reports error
 // information about the attempted delivery.
-func deliverRelayFailure(server Server, env Envelope, log *zap.Logger, to, errorStr string, sendErr error) {
+func (m *mta) deliverRelayFailure(env Envelope, log *zap.Logger, to, errorStr string, sendErr error) {
        log.Error(errorStr, zap.Error(sendErr))
 
        buf := &bytes.Buffer{}
@@ -153,5 +153,5 @@ func deliverRelayFailure(server Server, env Envelope, log *zap.Logger, to, error
        mw.Close()
 
        failure.Data = buf.Bytes()
-       server.DeliverMessage(failure)
+       m.server.DeliverMessage(failure)
 }
index cbced01facf89def5936f4cb0665389c8faedddb..c6a116b9c81b039b83d7d58cf8c8c57138268bfe 100644 (file)
@@ -45,7 +45,11 @@ func TestRelayRoundTrip(t *testing.T) {
        }
 
        host, port, _ := net.SplitHostPort(l.Addr().String())
-       relayMessageToHost(s, env, zap.NewNop(), env.RcptTo[0].Address, host, port)
+       mta := mta{
+               server: s,
+               log:    zap.NewNop(),
+       }
+       mta.relayMessageToHost(env, zap.NewNop(), env.RcptTo[0].Address, host, port)
 
        if want, got := 1, len(s.messages); want != got {
                t.Errorf("Want %d message to be delivered, got %d", want, got)
@@ -84,7 +88,11 @@ func TestDeliveryFailureMessage(t *testing.T) {
 
        errorStr1 := "internal message"
        errorStr2 := "general error 122"
-       deliverRelayFailure(s, env, zap.NewNop(), env.RcptTo[0].Address, errorStr1, fmt.Errorf(errorStr2))
+       mta := mta{
+               server: s,
+               log:    zap.NewNop(),
+       }
+       mta.deliverRelayFailure(env, zap.NewNop(), env.RcptTo[0].Address, errorStr1, fmt.Errorf(errorStr2))
 
        if want, got := 1, len(s.messages); want != got {
                t.Errorf("Want %d failure notification, got %d", want, got)
index 0f72e324e750cd708da0c58fd8c8ae962918da28..d14c2b1ef9b7a49adb3b837c8164f3b17190c24c 100644 (file)
@@ -16,6 +16,8 @@ import (
        "regexp"
        "strings"
        "time"
+
+       "go.uber.org/zap"
 )
 
 type ReplyLine struct {
@@ -89,12 +91,26 @@ func lookupRemoteHost(addr net.Addr) string {
        return rhost
 }
 
+// Server provides an interface for handling incoming SMTP requests via
+// AcceptConnection.
 type Server interface {
+       // Returns the name of the server, to use in HELO advertisements.
        Name() string
+
+       // If non-nil, enables STARTTLS support on the SMTP server with the given
+       // configuration.
        TLSConfig() *tls.Config
+
+       // Returns an status line indicating whether this server can receive
+       // mail for the specified email address.
        VerifyAddress(mail.Address) ReplyLine
-       // Verify that the authc+passwd identity can send mail as authz.
+
+       // Verify that the authc+passwd identity can send mail as authz on this
+       // server.
        Authenticate(authz, authc, passwd string) bool
+
+       // Delivers a valid incoming message to a recipient on this server. The
+       // addressee has been validated via VerifyAddress.
        DeliverMessage(Envelope) *ReplyLine
 
        // RelayMessage instructs the server to send the Envelope to another
@@ -102,6 +118,28 @@ type Server interface {
        RelayMessage(Envelope)
 }
 
+// MTA (Mail Transport Agent) allows a Server to interface with other SMTP
+// MTAs.
+type MTA interface {
+       // RelayMessage will attempt to send the specified Envelope. It will ask the
+       // Server to dial the MX servers for the addresses in Envelope.RcptTo for
+       // delivery. If relaying fails, a failure notice will be sent to the sender
+       // via Server.DeliverMessage.
+       RelayMessage(Envelope)
+}
+
+func NewDefaultMTA(server Server, log *zap.Logger) MTA {
+       return &mta{
+               server: server,
+               log:    log,
+       }
+}
+
+type mta struct {
+       server Server
+       log    *zap.Logger
+}
+
 type EmptyServerCallbacks struct{}
 
 func (*EmptyServerCallbacks) TLSConfig() *tls.Config {