"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))
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
}
}
// 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{}
mw.Close()
failure.Data = buf.Bytes()
- server.DeliverMessage(failure)
+ m.server.DeliverMessage(failure)
}
}
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)
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)
"regexp"
"strings"
"time"
+
+ "go.uber.org/zap"
)
type ReplyLine struct {
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
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 {