MAS-WP-0005-T12: gateway reads trusted A-R verdict; ticket service replies only on DMARC pass
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 252470@bnt-lap001 Assistant-Session: ad628c8f-4be2-4979-94c9-76c7ed6f8241
This commit is contained in:
parent
ab3346d187
commit
cca58d41fb
6 changed files with 128 additions and 13 deletions
|
|
@ -29,7 +29,45 @@ from mas_core.statute import build_statute
|
|||
EVENT_VERSION = "0.1"
|
||||
|
||||
|
||||
def message_meta_from_rfc822(raw: bytes) -> dict:
|
||||
def authentication_from_headers(headers: list[tuple[str, str]], trusted_authserv_id: Optional[str]) -> Optional[dict]:
|
||||
"""Sender-authentication verdict from our own MTA's Authentication-Results.
|
||||
|
||||
Only A-R headers whose authserv-id is `trusted_authserv_id` count, and only
|
||||
those above the first Received header, i.e. added at our hop (RFC 8601
|
||||
section 5). Anything a sender brought along sits below that line. The MTA
|
||||
also strips incoming A-R headers claiming our id; this is the second layer.
|
||||
"""
|
||||
if not trusted_authserv_id:
|
||||
return None
|
||||
want = trusted_authserv_id.lower()
|
||||
verdict: dict = {}
|
||||
for name, value in headers:
|
||||
lname = name.lower()
|
||||
if lname == "received":
|
||||
break
|
||||
if lname != "authentication-results":
|
||||
continue
|
||||
authserv, _, results = str(value).partition(";")
|
||||
ids = authserv.split()
|
||||
if not ids or ids[0].lower() != want:
|
||||
continue
|
||||
for clause in results.split(";"):
|
||||
words = clause.split()
|
||||
if not words or "=" not in words[0]:
|
||||
continue
|
||||
method, result = words[0].split("=", 1)
|
||||
method, result = method.lower(), result.lower()
|
||||
if method in ("dmarc", "spf", "dkim"):
|
||||
# several DKIM signatures: one pass is enough
|
||||
if not (method == "dkim" and verdict.get("dkim") == "pass"):
|
||||
verdict[method] = result
|
||||
for w in words[1:]:
|
||||
if method == "dmarc" and w.lower().startswith("header.from="):
|
||||
verdict["header_from"] = w.split("=", 1)[1]
|
||||
return verdict or None
|
||||
|
||||
|
||||
def message_meta_from_rfc822(raw: bytes, trusted_authserv_id: Optional[str] = None) -> dict:
|
||||
msg: EmailMessage = BytesParser(policy=policy.default).parsebytes(raw)
|
||||
body = msg.get_body(preferencelist=("plain",))
|
||||
text = body.get_content() if body is not None else ""
|
||||
|
|
@ -43,6 +81,7 @@ def message_meta_from_rfc822(raw: bytes) -> dict:
|
|||
"date": str(msg.get("Date", "")) or None,
|
||||
"auto_submitted": str(msg.get("Auto-Submitted", "")) or None,
|
||||
"precedence": str(msg.get("Precedence", "")) or None,
|
||||
"authentication": authentication_from_headers(msg.items(), trusted_authserv_id),
|
||||
"text": text,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue