do not quarantine an email if fails DMARC but has a small rspamd score (#1337)
* do not quarantine an email if fails DMARC but has a small rspamd score * use 0 when cannot parse rspamd score * use -1 as default value
This commit is contained in:
parent
5088604bb8
commit
1c5a547cd0
|
@ -504,6 +504,15 @@ if not RECOVERY_CODE_HMAC_SECRET or len(RECOVERY_CODE_HMAC_SECRET) < 16:
|
|||
"Please define RECOVERY_CODE_HMAC_SECRET in your configuration with a random string at least 16 chars long"
|
||||
)
|
||||
|
||||
|
||||
# the minimum rspamd spam score above which emails that fail DMARC should be quarantined
|
||||
if "MIN_RSPAMD_SCORE_FOR_FAILED_DMARC" in os.environ:
|
||||
MIN_RSPAMD_SCORE_FOR_FAILED_DMARC = float(
|
||||
os.environ["MIN_RSPAMD_SCORE_FOR_FAILED_DMARC"]
|
||||
)
|
||||
else:
|
||||
MIN_RSPAMD_SCORE_FOR_FAILED_DMARC = None
|
||||
|
||||
# run over all reverse alias for an alias and replace them with sender address
|
||||
ENABLE_ALL_REVERSE_ALIAS_REPLACEMENT = (
|
||||
"ENABLE_ALL_REVERSE_ALIAS_REPLACEMENT" in os.environ
|
||||
|
|
|
@ -5,7 +5,7 @@ from typing import Optional, Tuple
|
|||
from aiosmtpd.handlers import Message
|
||||
from aiosmtpd.smtp import Envelope
|
||||
|
||||
from app import s3
|
||||
from app import s3, config
|
||||
from app.config import (
|
||||
DMARC_CHECK_ENABLED,
|
||||
ALERT_QUARANTINE_DMARC,
|
||||
|
@ -34,6 +34,37 @@ def apply_dmarc_policy_for_forward_phase(
|
|||
|
||||
from_header = get_header_unicode(msg[headers.FROM])
|
||||
|
||||
warning_plain_text = f"""This email failed anti-phishing checks when it was received by SimpleLogin, be careful with its content.
|
||||
More info on https://simplelogin.io/docs/getting-started/anti-phishing/
|
||||
"""
|
||||
warning_html = f"""
|
||||
<p style="color:red">
|
||||
This email failed anti-phishing checks when it was received by SimpleLogin, be careful with its content.
|
||||
More info on <a href="https://simplelogin.io/docs/getting-started/anti-phishing/">anti-phishing measure</a>
|
||||
</p>
|
||||
"""
|
||||
|
||||
# do not quarantine an email if fails DMARC but has a small rspamd score
|
||||
if (
|
||||
config.MIN_RSPAMD_SCORE_FOR_FAILED_DMARC is not None
|
||||
and spam_result.rspamd_score < config.MIN_RSPAMD_SCORE_FOR_FAILED_DMARC
|
||||
and spam_result.dmarc
|
||||
in (
|
||||
DmarcCheckResult.quarantine,
|
||||
DmarcCheckResult.reject,
|
||||
)
|
||||
):
|
||||
LOG.w(
|
||||
f"email fails DMARC but has a small rspamd score, from contact {contact.email} to alias {alias.email}."
|
||||
f"mail_from:{envelope.mail_from}, from_header: {from_header}"
|
||||
)
|
||||
changed_msg = add_header(
|
||||
msg,
|
||||
warning_plain_text,
|
||||
warning_html,
|
||||
)
|
||||
return changed_msg, None
|
||||
|
||||
if spam_result.dmarc == DmarcCheckResult.soft_fail:
|
||||
LOG.w(
|
||||
f"dmarc forward: soft_fail from contact {contact.email} to alias {alias.email}."
|
||||
|
@ -41,15 +72,8 @@ def apply_dmarc_policy_for_forward_phase(
|
|||
)
|
||||
changed_msg = add_header(
|
||||
msg,
|
||||
f"""This email failed anti-phishing checks when it was received by SimpleLogin, be careful with its content.
|
||||
More info on https://simplelogin.io/docs/getting-started/anti-phishing/
|
||||
""",
|
||||
f"""
|
||||
<p style="color:red">
|
||||
This email failed anti-phishing checks when it was received by SimpleLogin, be careful with its content.
|
||||
More info on <a href="https://simplelogin.io/docs/getting-started/anti-phishing/">anti-phishing measure</a>
|
||||
</p>
|
||||
""",
|
||||
warning_plain_text,
|
||||
warning_html,
|
||||
)
|
||||
return changed_msg, None
|
||||
|
||||
|
@ -133,6 +157,7 @@ def apply_dmarc_policy_for_reply_phase(
|
|||
DmarcCheckResult.soft_fail,
|
||||
):
|
||||
return None
|
||||
|
||||
LOG.w(
|
||||
f"dmarc reply: Put email from {alias_from.email} to {contact_recipient} into quarantine. {spam_result.event_data()}, "
|
||||
f"mail_from:{envelope.mail_from}, from_header: {msg[headers.FROM]}"
|
||||
|
|
|
@ -4,6 +4,7 @@ from typing import Dict, Optional
|
|||
import newrelic.agent
|
||||
|
||||
from app.email import headers
|
||||
from app.log import LOG
|
||||
from app.models import EnumE, Phase
|
||||
from email.message import Message
|
||||
|
||||
|
@ -55,6 +56,7 @@ class SpamdResult:
|
|||
self.phase: Phase = phase
|
||||
self.dmarc: DmarcCheckResult = DmarcCheckResult.not_available
|
||||
self.spf: SPFCheckResult = SPFCheckResult.not_available
|
||||
self.rspamd_score = -1
|
||||
|
||||
def set_dmarc_result(self, dmarc_result: DmarcCheckResult):
|
||||
self.dmarc = dmarc_result
|
||||
|
@ -85,6 +87,7 @@ class SpamdResult:
|
|||
spam_entries = [
|
||||
entry.strip() for entry in str(spam_result_header[-1]).split("\n")
|
||||
]
|
||||
|
||||
for entry_pos in range(len(spam_entries)):
|
||||
sep = spam_entries[entry_pos].find("(")
|
||||
if sep > -1:
|
||||
|
@ -101,6 +104,17 @@ class SpamdResult:
|
|||
spamd_result.set_spf_result(spf_result)
|
||||
break
|
||||
|
||||
# parse the rspamd score
|
||||
try:
|
||||
score_line = spam_entries[0] # e.g. "default: False [2.30 / 13.00];"
|
||||
spamd_result.rspamd_score = float(
|
||||
score_line[(score_line.find("[") + 1) : score_line.find("]")]
|
||||
.split("/")[0]
|
||||
.strip()
|
||||
)
|
||||
except (IndexError, ValueError):
|
||||
LOG.e("cannot parse rspamd score")
|
||||
|
||||
cls._store_in_message(spamd_result, msg)
|
||||
return spamd_result
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
X-SimpleLogin-Client-IP: 54.39.200.130
|
||||
Received-SPF: Softfail (mailfrom) identity=mailfrom; client-ip=34.59.200.130;
|
||||
helo=relay.somewhere.net; envelope-from=everwaste@gmail.com;
|
||||
receiver=<UNKNOWN>
|
||||
Received: from relay.somewhere.net (relay.somewhere.net [34.59.200.130])
|
||||
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
|
||||
(No client certificate requested)
|
||||
by mx1.sldev.ovh (Postfix) with ESMTPS id 6D8C13F069
|
||||
for <wehrman_mannequin@sldev.ovh>; Thu, 17 Mar 2022 16:50:20 +0000 (UTC)
|
||||
Date: Thu, 17 Mar 2022 16:50:18 +0000
|
||||
To: {{ alias_email }}
|
||||
From: spoofedemailsource@gmail.com
|
||||
Subject: test Thu, 17 Mar 2022 16:50:18 +0000
|
||||
Message-Id: <20220317165018.000191@somewhere-5488dd4b6b-7crp6>
|
||||
X-Mailer: swaks v20201014.0 jetmore.org/john/code/swaks/
|
||||
X-Rspamd-Queue-Id: 6D8C13F069
|
||||
X-Rspamd-Server: staging1
|
||||
X-Spamd-Result: default: False [WRONGLY_FORMATTED / 13.00];
|
||||
MID_RHS_NOT_FQDN(0.50)[];
|
||||
DMARC_POLICY_SOFTFAIL(0.10)[gmail.com : No valid SPF, No valid DKIM,none];
|
||||
MIME_GOOD(-0.10)[text/plain];
|
||||
MIME_TRACE(0.00)[0:+];
|
||||
FROM_EQ_ENVFROM(0.00)[];
|
||||
ASN(0.00)[asn:16276, ipnet:34.59.0.0/16, country:FR];
|
||||
R_DKIM_NA(0.00)[];
|
||||
RCVD_COUNT_ZERO(0.00)[0];
|
||||
FREEMAIL_ENVFROM(0.00)[gmail.com];
|
||||
FROM_NO_DN(0.00)[];
|
||||
R_SPF_SOFTFAIL(0.00)[~all];
|
||||
FORCE_ACTION_SL_SPF_FAIL_ADD_HEADER(0.00)[add header];
|
||||
RCPT_COUNT_ONE(0.00)[1];
|
||||
FREEMAIL_FROM(0.00)[gmail.com];
|
||||
TO_DN_NONE(0.00)[];
|
||||
TO_MATCH_ENVRCPT_ALL(0.00)[];
|
||||
ARC_NA(0.00)[]
|
||||
X-Rspamd-Pre-Result: action=add header;
|
||||
module=force_actions;
|
||||
unknown reason
|
||||
X-Spam: Yes
|
||||
|
||||
This is a test mailing
|
|
@ -5,6 +5,7 @@ from tests.utils import load_eml_file
|
|||
def test_dmarc_result_softfail():
|
||||
msg = load_eml_file("dmarc_gmail_softfail.eml")
|
||||
assert DmarcCheckResult.soft_fail == SpamdResult.extract_from_headers(msg).dmarc
|
||||
assert SpamdResult.extract_from_headers(msg).rspamd_score == 0.5
|
||||
|
||||
|
||||
def test_dmarc_result_quarantine():
|
||||
|
@ -32,3 +33,14 @@ def test_dmarc_result_bad_policy():
|
|||
assert SpamdResult._get_from_message(msg) is None
|
||||
assert DmarcCheckResult.bad_policy == SpamdResult.extract_from_headers(msg).dmarc
|
||||
assert SpamdResult._get_from_message(msg) is not None
|
||||
|
||||
|
||||
def test_parse_rspamd_score():
|
||||
msg = load_eml_file("dmarc_gmail_softfail.eml")
|
||||
assert SpamdResult.extract_from_headers(msg).rspamd_score == 0.5
|
||||
|
||||
|
||||
def test_cannot_parse_rspamd_score():
|
||||
msg = load_eml_file("dmarc_cannot_parse_rspamd_score.eml")
|
||||
# use the default score when cannot parse
|
||||
assert SpamdResult.extract_from_headers(msg).rspamd_score == -1
|
||||
|
|
Loading…
Reference in New Issue