1package smtpserver
2
3import (
4 "bytes"
5 "context"
6 "crypto/sha256"
7 "fmt"
8 "io"
9 "log/slog"
10 "os"
11
12 "github.com/mjl-/bstore"
13
14 "github.com/mjl-/mox/message"
15 "github.com/mjl-/mox/mlog"
16 "github.com/mjl-/mox/moxio"
17 "github.com/mjl-/mox/store"
18)
19
20// rejectPresent returns whether the message is already present in the rejects mailbox.
21func rejectPresent(log mlog.Log, acc *store.Account, rejectsMailbox string, m *store.Message, f *os.File) (present bool, msgID string, hash []byte, rerr error) {
22 if p, err := message.Parse(log.Logger, false, store.FileMsgReader(m.MsgPrefix, f)); err != nil {
23 log.Infox("parsing reject message for message-id", err)
24 } else if header, err := p.Header(); err != nil {
25 log.Infox("parsing reject message header for message-id", err)
26 } else {
27 msgID, _, err = message.MessageIDCanonical(header.Get("Message-Id"))
28 if err != nil {
29 log.Debugx("parsing message-id for reject", err, slog.String("messageid", header.Get("Message-Id")))
30 }
31 }
32
33 // We must not read MsgPrefix, it will likely change for subsequent deliveries.
34 h := sha256.New()
35 if _, err := io.Copy(h, &moxio.AtReader{R: f}); err != nil {
36 log.Infox("copying reject message to hash", err)
37 } else {
38 hash = h.Sum(nil)
39 }
40
41 if msgID == "" && len(hash) == 0 {
42 return false, "", nil, fmt.Errorf("no message-id or hash for determining reject message presence")
43 }
44
45 var exists bool
46 var err error
47 acc.WithRLock(func() {
48 err = acc.DB.Read(context.TODO(), func(tx *bstore.Tx) error {
49 mbq := bstore.QueryTx[store.Mailbox](tx)
50 mbq.FilterNonzero(store.Mailbox{Name: rejectsMailbox})
51 mb, err := mbq.Get()
52 if err == bstore.ErrAbsent {
53 return nil
54 }
55 if err != nil {
56 return fmt.Errorf("looking for rejects mailbox: %w", err)
57 }
58
59 q := bstore.QueryTx[store.Message](tx)
60 q.FilterNonzero(store.Message{MailboxID: mb.ID})
61 q.FilterEqual("Expunged", false)
62 q.FilterFn(func(m store.Message) bool {
63 return msgID != "" && m.MessageID == msgID || len(hash) > 0 && bytes.Equal(m.MessageHash, hash)
64 })
65 exists, err = q.Exists()
66 return err
67 })
68 })
69 if err != nil {
70 return false, "", nil, fmt.Errorf("querying for presence of reject message: %w", err)
71 }
72 return exists, msgID, hash, nil
73}
74