]>
src.bluestatic.org Git - mailpopbox.git/blob - smtp/relay.go
2 // Copyright 2020 Blue Static <https://www.bluestatic.org>
3 // This program is free software licensed under the GNU General Public License,
4 // version 3.0. The full text of the license can be found in LICENSE.txt.
5 // SPDX-License-Identifier: GPL-3.0-only
23 func RelayMessage(server Server
, env Envelope
, log
*zap
.Logger
) {
24 for _
, rcptTo
:= range env
.RcptTo
{
25 sendLog
:= log
.With(zap
.String("address", rcptTo
.Address
))
27 domain
:= DomainForAddress(rcptTo
)
28 mx
, err
:= net
.LookupMX(domain
)
29 if err
!= nil ||
len(mx
) < 1 {
30 deliverRelayFailure(server
, env
, log
, rcptTo
.Address
, "failed to lookup MX records", err
)
33 relayMessageToHost(server
, env
, sendLog
, rcptTo
.Address
, mx
[0].Host
, "25")
37 func relayMessageToHost(server Server
, env Envelope
, log
*zap
.Logger
, to
, host
, port
string) {
38 from
:= env
.MailFrom
.Address
39 hostPort
:= net
.JoinHostPort(host
, port
)
40 log
= log
.With(zap
.String("host", hostPort
))
42 c
, err
:= smtp
.Dial(hostPort
)
44 // TODO - retry, or look at other MX records
45 deliverRelayFailure(server
, env
, log
, to
, "failed to dial host", err
)
50 if err
= c
.Hello(server
.Name()); err
!= nil {
51 deliverRelayFailure(server
, env
, log
, to
, "failed to HELO", err
)
55 if hasTls
, _
:= c
.Extension("STARTTLS"); hasTls
{
56 config
:= &tls
.Config
{ServerName
: host
}
57 if err
= c
.StartTLS(config
); err
!= nil {
58 deliverRelayFailure(server
, env
, log
, to
, "failed to STARTTLS", err
)
63 if err
= c
.Mail(from
); err
!= nil {
64 deliverRelayFailure(server
, env
, log
, to
, "failed MAIL FROM", err
)
68 if err
= c
.Rcpt(to
); err
!= nil {
69 deliverRelayFailure(server
, env
, log
, to
, "failed to RCPT TO", err
)
75 deliverRelayFailure(server
, env
, log
, to
, "failed to DATA", err
)
79 _
, err
= wc
.Write(env
.Data
)
82 deliverRelayFailure(server
, env
, log
, to
, "failed to write DATA", err
)
86 if err
= wc
.Close(); err
!= nil {
87 deliverRelayFailure(server
, env
, log
, to
, "failed to close DATA", err
)
92 // deliverRelayFailure logs and generates a delivery status notification. It
93 // writes to |log| the |errorStr| and |sendErr|, as well as preparing a new
94 // message, based of |env|, delivered to |server| that reports error
95 // information about the attempted delivery.
96 func deliverRelayFailure(server Server
, env Envelope
, log
*zap
.Logger
, to
, errorStr
string, sendErr error
) {
97 log
.Error(errorStr
, zap
.Error(sendErr
))
99 buf
:= &bytes
.Buffer
{}
100 mw
:= multipart
.NewWriter(buf
)
105 MailFrom
: mail
.Address
{"mailpopbox", "mailbox@" + DomainForAddress(env
.MailFrom
)},
106 RcptTo
: []mail
.Address
{env
.MailFrom
},
107 ID
: generateEnvelopeId("f", now
),
111 fmt
.Fprintf(buf
, "From: %s\n", failure
.MailFrom
.String())
112 fmt
.Fprintf(buf
, "To: %s\n", failure
.RcptTo
[0].String())
113 fmt
.Fprintf(buf
, "Subject: Delivery Status Notification (Failure)\n")
114 fmt
.Fprintf(buf
, "X-Failed-Recipients: %s\n", to
)
115 fmt
.Fprintf(buf
, "Message-ID: %s\n", failure
.ID
)
116 fmt
.Fprintf(buf
, "Date: %s\n", now
.Format(time
.RFC1123Z
))
117 fmt
.Fprintf(buf
, "Content-Type: multipart/report; boundary=%s; report-type=delivery-status\n\n", mw
.Boundary())
119 tw
, err
:= mw
.CreatePart(textproto
.MIMEHeader
{
120 "Content-Type": []string{"text/plain; charset=UTF-8"},
123 log
.Error("failed to create multipart 0", zap
.Error(err
))
126 fmt
.Fprintf(tw
, "* * * Delivery Failure * * *\n\n")
127 fmt
.Fprintf(tw
, "The server failed to relay the message:\n\n%s:\n%s\n", errorStr
, sendErr
.Error())
129 sw
, err
:= mw
.CreatePart(textproto
.MIMEHeader
{
130 "Content-Type": []string{"message/delivery-status"},
133 log
.Error("failed to create multipart 1", zap
.Error(err
))
136 fmt
.Fprintf(sw
, "Original-Envelope-ID: %s\n", env
.ID
)
137 fmt
.Fprintf(sw
, "Reporting-UA: %s\n", env
.EHLO
)
138 if env
.RemoteAddr
!= nil {
139 fmt
.Fprintf(sw
, "Reporting-MTA: dns; %s\n", lookupRemoteHost(env
.RemoteAddr
))
141 fmt
.Fprintf(sw
, "Date: %s\n", env
.Received
.Format(time
.RFC1123Z
))
143 ocw
, err
:= mw
.CreatePart(textproto
.MIMEHeader
{
144 "Content-Type": []string{"message/rfc822"},
147 log
.Error("failed to create multipart 2", zap
.Error(err
))
155 failure
.Data
= buf
.Bytes()
156 server
.OnMessageDelivered(failure
)