1package queue
2
3import (
4 "bufio"
5 "fmt"
6 "os"
7 "time"
8
9 "github.com/mjl-/mox/dns"
10 "github.com/mjl-/mox/dsn"
11 "github.com/mjl-/mox/message"
12 "github.com/mjl-/mox/mlog"
13 "github.com/mjl-/mox/mox-"
14 "github.com/mjl-/mox/smtp"
15 "github.com/mjl-/mox/store"
16)
17
18func queueDSNFailure(log *mlog.Log, m Msg, remoteMTA dsn.NameIP, secodeOpt, errmsg string) {
19 const subject = "mail delivery failed"
20 message := fmt.Sprintf(`
21Delivery has failed permanently for your email to:
22
23 %s
24
25No further deliveries will be attempted.
26
27Error during the last delivery attempt:
28
29 %s
30`, m.Recipient().XString(m.SMTPUTF8), errmsg)
31
32 queueDSN(log, m, remoteMTA, secodeOpt, errmsg, true, nil, subject, message)
33}
34
35func queueDSNDelay(log *mlog.Log, m Msg, remoteMTA dsn.NameIP, secodeOpt, errmsg string, retryUntil time.Time) {
36 const subject = "mail delivery delayed"
37 message := fmt.Sprintf(`
38Delivery has been delayed of your email to:
39
40 %s
41
42Next attempts to deliver: in 4 hours, 8 hours and 16 hours.
43If these attempts all fail, you will receive a notice.
44
45Error during the last delivery attempt:
46
47 %s
48`, m.Recipient().XString(false), errmsg)
49
50 queueDSN(log, m, remoteMTA, secodeOpt, errmsg, false, &retryUntil, subject, message)
51}
52
53// We only queue DSNs for delivery failures for emails submitted by authenticated
54// users. So we are delivering to local users. ../rfc/5321:1466
55// ../rfc/5321:1494
56// ../rfc/7208:490
57// todo future: when we implement relaying, we should be able to send DSNs to non-local users. and possibly specify a null mailfrom. ../rfc/5321:1503
58func queueDSN(log *mlog.Log, m Msg, remoteMTA dsn.NameIP, secodeOpt, errmsg string, permanent bool, retryUntil *time.Time, subject, textBody string) {
59 kind := "delayed delivery"
60 if permanent {
61 kind = "failure"
62 }
63
64 qlog := func(text string, err error) {
65 log.Errorx("queue dsn: "+text+": sender will not be informed about dsn", err, mlog.Field("sender", m.Sender().XString(m.SMTPUTF8)), mlog.Field("kind", kind))
66 }
67
68 msgf, err := os.Open(m.MessagePath())
69 if err != nil {
70 qlog("opening queued message", err)
71 return
72 }
73 msgr := store.FileMsgReader(m.MsgPrefix, msgf)
74 defer func() {
75 err := msgr.Close()
76 log.Check(err, "closing message reader after queuing dsn")
77 }()
78 headers, err := message.ReadHeaders(bufio.NewReader(msgr))
79 if err != nil {
80 qlog("reading headers of queued message", err)
81 return
82 }
83
84 var action dsn.Action
85 var status string
86 if permanent {
87 status = "5."
88 action = dsn.Failed
89 } else {
90 action = dsn.Delayed
91 status = "4."
92 }
93 if secodeOpt != "" {
94 status += secodeOpt
95 } else {
96 status += "0.0"
97 }
98 diagCode := errmsg
99 if !dsn.HasCode(diagCode) {
100 diagCode = status + " " + errmsg
101 }
102
103 dsnMsg := &dsn.Message{
104 SMTPUTF8: m.SMTPUTF8,
105 From: smtp.Path{Localpart: "postmaster", IPDomain: dns.IPDomain{Domain: mox.Conf.Static.HostnameDomain}},
106 To: m.Sender(),
107 Subject: subject,
108 References: m.MessageID,
109 TextBody: textBody,
110
111 ReportingMTA: mox.Conf.Static.HostnameDomain.ASCII,
112 ArrivalDate: m.Queued,
113
114 Recipients: []dsn.Recipient{
115 {
116 FinalRecipient: m.Recipient(),
117 Action: action,
118 Status: status,
119 RemoteMTA: remoteMTA,
120 DiagnosticCode: diagCode,
121 LastAttemptDate: *m.LastAttempt,
122 WillRetryUntil: retryUntil,
123 },
124 },
125
126 Original: headers,
127 }
128 msgData, err := dsnMsg.Compose(log, m.SMTPUTF8)
129 if err != nil {
130 qlog("composing dsn", err)
131 return
132 }
133
134 msgData = append(msgData, []byte("Return-Path: <"+dsnMsg.From.XString(m.SMTPUTF8)+">\r\n")...)
135
136 mailbox := "Inbox"
137 acc, err := store.OpenAccount(m.SenderAccount)
138 if err != nil {
139 acc, err = store.OpenAccount(mox.Conf.Static.Postmaster.Account)
140 if err != nil {
141 qlog("looking up postmaster account after sender account was not found", err)
142 return
143 }
144 mailbox = mox.Conf.Static.Postmaster.Mailbox
145 }
146 defer func() {
147 err := acc.Close()
148 log.Check(err, "queue dsn: closing account", mlog.Field("sender", m.Sender().XString(m.SMTPUTF8)), mlog.Field("kind", kind))
149 }()
150
151 msgFile, err := store.CreateMessageTemp("queue-dsn")
152 if err != nil {
153 qlog("creating temporary message file", err)
154 return
155 }
156 defer func() {
157 if msgFile != nil {
158 err := os.Remove(msgFile.Name())
159 log.Check(err, "removing message file", mlog.Field("path", msgFile.Name()))
160 err = msgFile.Close()
161 log.Check(err, "closing message file")
162 }
163 }()
164
165 msgWriter := message.NewWriter(msgFile)
166 if _, err := msgWriter.Write(msgData); err != nil {
167 qlog("writing dsn message", err)
168 return
169 }
170
171 msg := &store.Message{
172 Received: time.Now(),
173 Size: msgWriter.Size,
174 MsgPrefix: []byte{},
175 }
176 acc.WithWLock(func() {
177 if err := acc.DeliverMailbox(log, mailbox, msg, msgFile, true); err != nil {
178 qlog("delivering dsn to mailbox", err)
179 return
180 }
181 })
182 err = msgFile.Close()
183 log.Check(err, "closing dsn file")
184 msgFile = nil
185}
186