Skip to content

Add an option to silently reject#50

Closed
bobobo1618 wants to merge 5 commits into
mjl-:mainfrom
bobobo1618:reject_exceptions
Closed

Add an option to silently reject#50
bobobo1618 wants to merge 5 commits into
mjl-:mainfrom
bobobo1618:reject_exceptions

Conversation

@bobobo1618

Copy link
Copy Markdown
Contributor

Spam forwarded from my Gmail account is redelivered because mox rejects it. I want mox to process it as if it's spam but not tell Gmail, so it doesn't try to redeliver it.

@mjl-

mjl- commented Jul 24, 2023

Copy link
Copy Markdown
Owner

I hope I understand the situation correctly.

So gmail is forwarding messages coming to your @gmail.com account to your new address hosted on mox. And mox is rejecting some of those messages as spam. I suppose this causes gmail to put the forwarding rule on pause? Is the forwarding rule configured as "send a copy to this address", or more a "don't store anything in gmail anymore, just forward to my new address"? (I don't know if gmail has the second option). If I understand it correctly, gmail doesn't spamfilter the messages it forwards, or it doesn't do a good job? (I would expect it would).

I think the current approach in the PR would mostly work. But I think you would want to disable DMARC checks. Because gmail is sending messages to mox with a "mesage From" address for which it won't have an "aligned SPF pass" (either it uses the original "smtp mail from" that is aligned but not passes SPF, or it uses its own "smtp mail from" that passes SPF but isn't aligned). If there is also no (passing) DKIM header in the message, the DMARC check will fail. With the current PR changes, I think that would put such messages in the Rejects mailbox. I think you only want to do the soft rejecting based on sender/sender-domain reputation and content (bayesian). The ListAllowDomain option in the Ruleset is related, but disables all spam checking, it is intended for messages from mailing lists. The assumption is that mailing lists do good enough spam filtering. If we were to reject messages from a mailing list, our subscription would likely be cancelled. So it's quite similar to your situation, and you could probably use it, if only if gmail's spam filtering was good enough. I would think gmails were good enough, but perhaps those of other providers/setups would perhaps not be.

There are a few complications. Although you can recognize email coming from gmail (with a "VerifiedDomain", probably at least matched on verified SPF, perhaps also verified DKIM (does gmail add a DKIM header when forwarding?)), I don't know if you can be sure that it is a forwarded message versus a message someone sent directly to your new address from their own gmail address. They both come from gmail's mail servers. Your original gmail address doesn't necessarily have to be in the message To/Cc/Bcc header. Perhaps it is good enough to match for that, though the Ruleset currently doesn't allow for that matching (we can add it). I think you would have to accept that there could be some legitimate forwarded messages that mox would silently put in the Rejects mailbox.

Currently, this PR would add a knob "softreject" (the name doesn't feel quite right). Perhaps (I'm not sure), it could be better to name it after the scenario. The scenario being: For forwarded messages (matched on verified domain of forwarding server, matched original/forwarded addressee in message (in to/cc/bcc headers of original address), and matched forward-destination address), always accept messages from the forwarding server, but still apply some of mox's spam-checks and possibly deliver to the rejects mailbox instead of regular mailbox.

Another thought: If the previous KeepRejects-change was meant to work together with this, perhaps it would make sense to put the soft-rejected messages in a different mailbox than Rejects and not use the KeepRejects config? We could add a field in the Ruleset for the mailbox for such forwarded rejects, e.g. "ForwardRejects".

Comment thread store/account.go
@bobobo1618

Copy link
Copy Markdown
Contributor Author

Your understanding is generally correct. To add more background, I've set up Gmail auto-forwarding with a filter that forwards all incoming mail then deletes it. This ignores all spam filtering. Gmail is perfectly capable of detecting spam but I don't want to have to log in to Gmail to check my spam inbox for false-positives, I'd prefer all spam be forwarded to mox and handled there, in one place.

Right now though, mox (correctly) detects the spam but (incorrectly) rejects it, causing Gmail to attempt to send it again. I'd prefer to accept delivery in these cases so that Gmail and mox don't waste their energy.

This PR is working though, this is how I've configured mox:

Accounts:
	me:
		Domain: mydomain
		Destinations:
			me@mydomain:
				Rulesets:
					-
						VerifiedDomain: gmail.com
						HeadersRegexp:
							delivered-to: me.*@gmail.com
							return-path: me\+caf
						SoftReject: true

Gmail adds both SPF and DKIM when forwarding, so VerifiedDomain works just fine. It also adds Delivered-To and Return-Path headers that can be used to identify incoming forwarded emails and not allow the entire gmail.com domain.

The previous KeepRejects option is orthogonal to this, it's about spam in general, this is about a specific category.

@mjl-

mjl- commented Jul 24, 2023

Copy link
Copy Markdown
Owner

Thanks, clear, those delivered-to and return-path make sense, hadn't thought about those.

Doesn't this still leave an issue with mox rejecting due to DMARC fails? Or is gmail rewriting the message From header to your own gmail address?

@bobobo1618

Copy link
Copy Markdown
Contributor Author

Hmm, for a forward from Gmail, DMARC passes:

Authentication-Results: <myhost>; iprev=pass
	policy.iprev=<ipv6addr>; dkim=pass (2048 bit rsa)
	header.d=gmail.com header.s=20221208 header.a=rsa-sha256
	header.b=UkqH5qqu7/KZ; spf=pass smtp.mailfrom=gmail.com; dmarc=pass
	header.from=gmail.com

The From header is retained as-is.

But I have another email from a different address and authentication fails:

Authentication-Results: <myhost>; iprev=pass policy.iprev=<ip4addr>;
	dkim=none reason="no dkim signatures"; spf=pass smtp.mailfrom=gmail.com;
	dmarc=none header.from=<spamdomain>

I'll wait and see if I see rejections.

@mjl-

mjl- commented Jul 24, 2023

Copy link
Copy Markdown
Owner

If a message has a passing DKIM header in the message, the DMARC check will pass (assuming the "message From" address isn't rewritten). This is quite common nowadays, but not guaranteed. Plenty of mailing lists modify the message so that the DKIM signature becomes invalid. But I suspect you would update mailing list subscriptions to go directly to your new address.

So that leaves SPF. The Authentication-Results show a pass, but it won't be an aligned pass.

The second Authentication-Results has a dmarc=none. That probably means the "message From" domain doesn't have a dmarc (reject) policy, which makes sense for spammers. But valid domains may have dmarc reject policies. One would hope they would also add DKIM signatures to their messages, but there is no guarantee.

Anyway, it seems this option make sense to have, will get back to it soon.

@mjl-

mjl- commented Jul 26, 2023

Copy link
Copy Markdown
Owner

I gave this some more thought.

I think we have to be more careful about how forwarded messages influence mox's
spam filtering. In store.Message, we have fields for the remote server's
IP/subnet, and verified domains. We use those, and the junk/non-junk
classifications on messages, for spam evaluations on future messages. But for
forwarded messages, the IP and the verified domain of the forwarding server
should not influence spam evaluations. Both spams and hams are sent by the
forwarder, and it can't be held accountable. For forwarded messages,
we could leave the regular IP/subnet fields empty during delivery (but store
them in separate fields "ForwarderIP" and "ForwarderVerifiedDomain", just for
reference). Other signals, like other verified domains (DKIM), and the
(bayesian) content-based evaluation would still be used for
training/evaluation. To apply this policy, we need to know a message is a
forwarded message. We can add a field like "IsForward: true" to a ruleset to
enable this behaviour. Or change the config mechanism to be more high-level.

Ideas:

Rulesets:
	-
		# IsForward causes an accept of the message during the SMTP transaction, but if
		# the message is classified as spam, it is delivered to the Rejects mailbox. The
		# VerifiedDomain (if set) and IP address of the forwarding mail server isn't
		# stored/used in future spam evaluations.
		IsForward: true
		ForwardRejectsMailbox: ForwardRejects
		VerifiedDomain: gmail.com
		HeadersRegexp:
			delivered-to: me.*@gmail.com
			return-path: me\+caf

Or more high-level (my preference):

Rulesets:
	-
		# If the message is forwarded from the specified address (based on matching
		# localpart and domain with verified SMTP MAIL FROM address, and optionally
		# Delivered-To/Return-Path message headers), it is always accepted during the SMTP
		# transaction, but if classified as spam will be delivered to the Rejects mailbox.
		ForwardedFrom:
			LocalpartRegexp: me.*
			Domain: gmail.com
			# Optional alternative domain to verify. If empty, the Domain above must be verified for this ruleset to match.
			VerifiedDomain: ...
			UseDeliveredTo: true
			UseReturnPath: true
			# If set, the mailbox to deliver rejects to instead of the default accounts
			# RejectMailbox. Messages delivered to this mailbox are not automatically cleaned up,
			# unless set to the account's RejectsMailbox.
			RejectsMailbox: ForwardRejects

In case of forwarding, we would also skip the DMARC evaluation.

In the future, mox should also implement ARC
(https://en.wikipedia.org/wiki/Authenticated_Received_Chain), and for forwards
trust the ARC headers from the forwarding mail server. It will allow us to use
the reputation for domains that the forwarding server (claims to have)
verified, but we couldn't (e.g. SPF, or DKIM headers from before they were
mangled). To do so, we need to know if the intent of a ruleset is to handle a
forward. For gmail it may not make much of a difference most of the time
(gmail probably doesn't invalidate DKIM headers when forwarding, and there is a
good chance that the spf- and dkim-verified (sub)domain are the same for most
messages), but for other forwarding servers and some messages it may make a
difference.

With a ForwardedFrom config option, we can explain more fully in the sconf-doc
string what the consequences are and what users should configure.

Btw, have you tried matching the ruleset with SMTPMailFromRegexp? If gmail sets
it to your own address, that's probably a stricter way to detect forwards from
gmail vs regular mails from gmail. Senders can add any message header, but an
"SMTP mail from" from a verified domain is safer. The Return-Path header that
mox adds has the "SMTP MAIL FROM" address that gmail used for forwarding.

Some more notes:

  • We have to check in mox-/config.go that the RejectsMailbox is properly
    normalized, and not "Inbox", similar to the current RejectsMailbox. For
    SoftReject, a check that RejectsMailbox isn't empty would be a good idea.
  • The new Ruleset fields should be configurable through the account web
    interface. Without updating the account web interface, we would probably
    overwrite (clear) the new field when storing a ruleset through the account web
    interface.
  • With a per-Ruleset Rejects mailbox I think we'll need a new field WasReject on
    store.Message, to indicate it was initially a rejected message.
    The code currently has a few places (e.g. when moving messages between
    mailboxes through IMAP) where we check if the source mailbox has the same name
    as the configured RejectsMailbox, and update some fields so the updated
    classification is used for future spam evaluations. But with potentially many
    rejects mailboxes (which can change by updating the config, so historic
    rejects to a different mailbox in the past are lost currently!), a
    single-purpose boolean seems better.
  • There could still be an interaction with the Rejects mailbox that could give
    trouble. It has handling to not store duplicates, based on Message-ID header
    and hash of the message. It could interfere with the "Accept in SMTP
    transaction and deliver to Rejects mailbox" concept. Will turn up when implementing.

I can help with/implement these changes, it's becoming more than you probably
hoped for.

@bobobo1618

Copy link
Copy Markdown
Contributor Author

I've been thinking about this too but my conclusion is that it might make milter/Sieve support more important. I imagine there are going to be other people in the future who have cases like mine and to add explicit handling for those cases to Mox seems unmaintainable to me. Better would be to make Mox configurable enough that everyone can set it up for their particular use-case and adding milters and/or Sieve seems like the better solution to me.

Speaking of Sieve, it looks like maddy, a similar project, has Sieve support in pure Go through this library.

@mjl-

mjl- commented Aug 4, 2023

Copy link
Copy Markdown
Owner

I think mox will need to be more configurable in the future. But I also don't want every user to have to figure out the right combination of knobs to handle common cases. I think accepting forwarded emails is quite common. And interactions between email protocols/mechanisms is a bit more tricky than users would perhaps think. So I prefer to start with making it easy to configure this common case, and we'll get to the fully-configurable later.
Sieve is already a bit higher on the todo list. I haven't used it, but think one of the most important use cases of it is moving messages to a certain mailbox automatically. Mox can do that with a rule, but it's more helpful for migrating users if they can keep using their sieve scripts. Milter support is lower on the roadmap, it's a bit more complicated to support...
I'm working on a big feature now, hope to get to this forwarding config feature soon after.

mjl- added a commit that referenced this pull request Aug 9, 2023
… mailbox

soon, we can have multiple rejects mailboxes.  and checking against the
configured rejects mailbox name wasn't foolproof to begin with, because it may
have changed between delivery to the rejects mailbox and the message being
moved.

after upgrading, messages currently in rejects mailboxes don't have IsReject
set, so they don't get the special rejecs treatment when being moved. they are
removed from the rejects mailbox after some time though, and newly added
rejects will be treated correctly. so this means some existing messages wrongly
delivered to the rejects mailbox, and moved out, aren't used (for a positive
signal) for future deliveries.  saves a bit of complexity in the
implementation.  i think the tradeoff is worth it.

related to discussion in issue #50
mjl- added a commit that referenced this pull request Aug 9, 2023
…red 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.
mjl- added a commit that referenced this pull request Aug 9, 2023
…odifying how junk analysis is done

part of PR #50 by bobobo1618
@mjl-

mjl- commented Aug 9, 2023

Copy link
Copy Markdown
Owner

hi @bobobo1618, i went back to use pretty much your approach. you were right that these cases were better handled decoupled. so there is now the "AcceptRejectsToMailbox" option, that is quite close to this PR, but it is more a real accept (see commit message for reasoning). the second change is adding the IsForward field.

i tested this with a gmail account, set up forwarding like you described. the SMTP MAIL FROM is of the form myuser+.*@gmail.com, so i think it's best to set up a ruleset matching for SMTPMailFromRegexp for that.

interested in hearing if this works for you, otherwise we can make changes.
thanks for the PR and discussion, very helpful!

@bobobo1618

Copy link
Copy Markdown
Contributor Author

Sounds good to me! I'll close this then.

@bobobo1618 bobobo1618 closed this Aug 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants