10	"golang.org/x/exp/slog"
 
12	"github.com/prometheus/client_golang/prometheus"
 
13	"github.com/prometheus/client_golang/prometheus/promauto"
 
15	"github.com/mjl-/mox/dns"
 
16	"github.com/mjl-/mox/dsn"
 
17	"github.com/mjl-/mox/message"
 
18	"github.com/mjl-/mox/mlog"
 
19	"github.com/mjl-/mox/mox-"
 
20	"github.com/mjl-/mox/smtp"
 
21	"github.com/mjl-/mox/store"
 
25	metricDMARCReportFailure = promauto.NewCounter(
 
26		prometheus.CounterOpts{
 
27			Name: "mox_queue_dmarcreport_failure_total",
 
28			Help: "Permanent failures to deliver a DMARC report.",
 
33func deliverDSNFailure(ctx context.Context, log mlog.Log, m Msg, remoteMTA dsn.NameIP, secodeOpt, errmsg string) {
 
34	const subject = "mail delivery failed"
 
35	message := fmt.Sprintf(`
 
36Delivery has failed permanently for your email to:
 
40No further deliveries will be attempted.
 
42Error during the last delivery attempt:
 
45`, m.Recipient().XString(m.SMTPUTF8), errmsg)
 
47	deliverDSN(ctx, log, m, remoteMTA, secodeOpt, errmsg, true, nil, subject, message)
 
50func deliverDSNDelay(ctx context.Context, log mlog.Log, m Msg, remoteMTA dsn.NameIP, secodeOpt, errmsg string, retryUntil time.Time) {
 
51	// Should not happen, but doesn't hurt to prevent sending delayed delivery
 
52	// notifications for DMARC reports. We don't want to waste postmaster attention.
 
57	const subject = "mail delivery delayed"
 
58	message := fmt.Sprintf(`
 
59Delivery has been delayed of your email to:
 
63Next attempts to deliver: in 4 hours, 8 hours and 16 hours.
 
64If these attempts all fail, you will receive a notice.
 
66Error during the last delivery attempt:
 
69`, m.Recipient().XString(false), errmsg)
 
71	deliverDSN(ctx, log, m, remoteMTA, secodeOpt, errmsg, false, &retryUntil, subject, message)
 
74// We only queue DSNs for delivery failures for emails submitted by authenticated
 
78func deliverDSN(ctx context.Context, log mlog.Log, m Msg, remoteMTA dsn.NameIP, secodeOpt, errmsg string, permanent bool, retryUntil *time.Time, subject, textBody string) {
 
79	kind := "delayed delivery"
 
84	qlog := func(text string, err error) {
 
85		log.Errorx("queue dsn: "+text+": sender will not be informed about dsn", err, slog.String("sender", m.Sender().XString(m.SMTPUTF8)), slog.String("kind", kind))
 
88	msgf, err := os.Open(m.MessagePath())
 
90		qlog("opening queued message", err)
 
93	msgr := store.FileMsgReader(m.MsgPrefix, msgf)
 
96		log.Check(err, "closing message reader after queuing dsn")
 
98	headers, err := message.ReadHeaders(bufio.NewReader(msgr))
 
100		qlog("reading headers of queued message", err)
 
104	var action dsn.Action
 
119	if !dsn.HasCode(diagCode) {
 
120		diagCode = status + " " + errmsg
 
123	dsnMsg := &dsn.Message{
 
124		SMTPUTF8:   m.SMTPUTF8,
 
125		From:       smtp.Path{Localpart: "postmaster", IPDomain: dns.IPDomain{Domain: mox.Conf.Static.HostnameDomain}},
 
128		MessageID:  mox.MessageIDGen(false),
 
129		References: m.MessageID,
 
132		ReportingMTA: mox.Conf.Static.HostnameDomain.ASCII,
 
133		ArrivalDate:  m.Queued,
 
135		Recipients: []dsn.Recipient{
 
137				FinalRecipient:  m.Recipient(),
 
140				RemoteMTA:       remoteMTA,
 
141				DiagnosticCode:  diagCode,
 
142				LastAttemptDate: *m.LastAttempt,
 
143				WillRetryUntil:  retryUntil,
 
149	msgData, err := dsnMsg.Compose(log, m.SMTPUTF8)
 
151		qlog("composing dsn", err)
 
155	msgData = append([]byte("Return-Path: <"+dsnMsg.From.XString(m.SMTPUTF8)+">\r\n"), msgData...)
 
158	senderAccount := m.SenderAccount
 
160		// senderAccount should already by postmaster, but doesn't hurt to ensure it.
 
161		senderAccount = mox.Conf.Static.Postmaster.Account
 
163	acc, err := store.OpenAccount(log, senderAccount)
 
165		acc, err = store.OpenAccount(log, mox.Conf.Static.Postmaster.Account)
 
167			qlog("looking up postmaster account after sender account was not found", err)
 
170		mailbox = mox.Conf.Static.Postmaster.Mailbox
 
174		log.Check(err, "queue dsn: closing account", slog.String("sender", m.Sender().XString(m.SMTPUTF8)), slog.String("kind", kind))
 
177	msgFile, err := store.CreateMessageTemp(log, "queue-dsn")
 
179		qlog("creating temporary message file", err)
 
182	defer store.CloseRemoveTempFile(log, msgFile, "dsn message")
 
184	msgWriter := message.NewWriter(msgFile)
 
185	if _, err := msgWriter.Write(msgData); err != nil {
 
186		qlog("writing dsn message", err)
 
190	msg := &store.Message{
 
191		Received:  time.Now(),
 
192		Size:      msgWriter.Size,
 
196	// If this is a DMARC report, deliver it as seen message to a submailbox of the
 
197	// postmaster mailbox. We mark it as seen so it doesn't waste postmaster attention,
 
198	// but we deliver them so they can be checked in case of problems.
 
200		mailbox = fmt.Sprintf("%s/dmarc", mox.Conf.Static.Postmaster.Mailbox)
 
202		metricDMARCReportFailure.Inc()
 
203		log.Info("delivering dsn for failure to deliver outgoing dmarc report")
 
206	acc.WithWLock(func() {
 
207		if err := acc.DeliverMailbox(log, mailbox, msg, msgFile); err != nil {
 
208			qlog("delivering dsn to mailbox", err)