add option to ruleset to accept incoming spammy messages to a configured mailbox

this is based on @bobobo1618's PR #50. bobobo1618 had the right idea, i tried
including an "is forwarded email" configuration option but that indeed became
too tightly coupled. the "is forwarded" option is still planned, but it is
separate from the "accept rejects to mailbox" config option, because one could
still want to push back on forwarded spam messages.

we do an actual accept, delivering to a configured mailbox, instead of storing
to the rejects mailbox where messages can automatically be removed from.  one
of the goals of mox is not pretend to accept email while actually junking it.
users can still configure delivery to a junk folder (as was already possible),
but aren't deleted automatically. there is still an X-Mox-Reason header in the
message, and a log line about accepting the reject, but otherwise it is
registered and treated as an (smtp) accept.

the ruleset mailbox is still required to keep that explicit. users can specify
Inbox again.

hope this is good enough for PR #50, otherwise we'll change it.
This commit is contained in:
Mechiel Lukkien
2023-08-09 18:03:29 +02:00
parent 383fe4f53a
commit 9c31789c56
9 changed files with 98 additions and 35 deletions

View File

@ -72,6 +72,14 @@ Message-Id: <test@example.org>
test email
`, "\n", "\r\n")
var deliverMessage2 = strings.ReplaceAll(`From: <remote@example.org>
To: <mjl@mox.example>
Subject: test
Message-Id: <test2@example.org>
test email, unique.
`, "\n", "\r\n")
type testserver struct {
t *testing.T
acc *store.Account
@ -411,10 +419,10 @@ func TestSpam(t *testing.T) {
tinsertmsg(t, ts.acc, "Inbox", &nm, deliverMessage)
}
checkRejectsCount := func(expect int) {
checkCount := func(mailboxName string, expect int) {
t.Helper()
q := bstore.QueryDB[store.Mailbox](ctxbg, ts.acc.DB)
q.FilterNonzero(store.Mailbox{Name: "Rejects"})
q.FilterNonzero(store.Mailbox{Name: mailboxName})
mb, err := q.Get()
tcheck(t, err, "get rejects mailbox")
qm := bstore.QueryDB[store.Message](ctxbg, ts.acc.DB)
@ -439,8 +447,21 @@ func TestSpam(t *testing.T) {
t.Fatalf("delivery by bad sender, got err %v, expected smtpclient.Error with code %d", err, smtp.C451LocalErr)
}
// Message should now be in Rejects mailbox.
checkRejectsCount(1)
checkCount("Rejects", 1)
})
// Delivery from sender with bad reputation matching AcceptRejectsToMailbox should
// result in accepted delivery to the mailbox.
ts.run(func(err error, client *smtpclient.Client) {
mailFrom := "remote@example.org"
rcptTo := "mjl2@mox.example"
if err == nil {
err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(deliverMessage2)), strings.NewReader(deliverMessage2), false, false)
}
tcheck(t, err, "deliver")
checkCount("mjl2junk", 1) // In ruleset rejects mailbox.
checkCount("Rejects", 1) // Same as before.
})
// Mark the messages as having good reputation.
@ -458,8 +479,9 @@ func TestSpam(t *testing.T) {
}
tcheck(t, err, "deliver")
// Message should now be removed from Rejects mailbox.
checkRejectsCount(0)
// Message should now be removed from Rejects mailboxes.
checkCount("Rejects", 0)
checkCount("mjl2junk", 1)
})
// Undo dmarc pass, mark messages as junk, and train the filter.