From e88e952fccccf4815b629ec1af0b024bbba74a78 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sat, 11 May 2024 16:55:31 -0400 Subject: [PATCH] Begin refactoring out relay logic --- smtp/relay_smtp.go | 84 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/smtp/relay_smtp.go b/smtp/relay_smtp.go index 9bee431..15898ac 100644 --- a/smtp/relay_smtp.go +++ b/smtp/relay_smtp.go @@ -8,6 +8,7 @@ package smtp import ( "crypto/tls" + "fmt" "net" "net/smtp" @@ -44,37 +45,55 @@ func (m *smtpMTA) RelayMessage(env Envelope) { } func (m *smtpMTA) 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)) + log = log.With(zap.String("host", net.JoinHostPort(host, port))) - c, err := smtp.Dial(hostPort) + c, err := m.dialSMTP(host, port, false) if err != nil { // TODO - retry, or look at other MX records - m.RelayFailure(env, log, to, "failed to dial host", err) + m.RelayFailure(env, log, to, "failed to connect to MTA", err) return } defer c.Quit() + m.sendEnvelope(c, to, env, log) +} + +// dialSMTP establishes a connection to the SMTP server at `host`:`port`, attempting +// to establish a TLS connection. If `requireTLS` is true and the connection cannot +// be encrypted, returns an error. +func (m *smtpMTA) dialSMTP(host string, port string, requireTLS bool) (*smtp.Client, error) { + c, err := smtp.Dial(net.JoinHostPort(host, port)) + if err != nil { + return nil, err + } + if err = c.Hello(m.server.Name()); err != nil { - m.RelayFailure(env, log, to, "failed to HELO", err) - return + return nil, err } if hasTls, _ := c.Extension("STARTTLS"); hasTls { config := &tls.Config{ServerName: host} if err = c.StartTLS(config); err != nil { - m.RelayFailure(env, log, to, "failed to STARTTLS", err) - return + return nil, fmt.Errorf("failed to STARTTLS: %v", err) } + } else if requireTLS { + return nil, fmt.Errorf("server does not support TLS") } - if err = c.Mail(from); err != nil { + return c, nil +} + +// sendEnvelope writes an Envelope for a specific `to` recipient on an active +// SMTP client. +func (m *smtpMTA) sendEnvelope(c *smtp.Client, to string, env Envelope, log *zap.Logger) { + from := env.MailFrom.Address + + if err := c.Mail(from); err != nil { m.RelayFailure(env, log, to, "failed MAIL FROM", err) return } - if err = c.Rcpt(to); err != nil { + if err := c.Rcpt(to); err != nil { m.RelayFailure(env, log, to, "failed to RCPT TO", err) return } @@ -97,3 +116,46 @@ func (m *smtpMTA) relayMessageToHost(env Envelope, log *zap.Logger, to, host, po return } } + +type smtpRelayMTA struct { + smtpMTA + host, port string + user string + password string +} + +func NewSMTPRelayMTA(server Server, log *zap.Logger, host, port, user, password string) MTA { + return &smtpRelayMTA{ + smtpMTA: smtpMTA{ + mta: mta{ + server: server, + log: log, + }, + }, + host: host, + port: port, + user: user, + password: password, + } +} + +func (m *smtpRelayMTA) RelayMessage(env Envelope) { + log := m.log.With(zap.String("host", net.JoinHostPort(m.host, m.port))) + + c, err := m.dialSMTP(m.host, m.port, true) + if err != nil { + m.RelayFailure(env, log, "", "failed to connect to MTA", err) + return + } + defer c.Quit() + + auth := smtp.PlainAuth("", m.user, m.password, m.host) + if err = c.Auth(auth); err != nil { + m.RelayFailure(env, log, "", "failed to authenticate to relay", err) + return + } + + for _, rcptTo := range env.RcptTo { + m.sendEnvelope(c, rcptTo.Address, env, log) + } +} -- 2.52.0