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
|
|
@ -23,6 +23,7 @@ import argparse
|
|||
import json
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from typing import Optional
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
|
||||
from .core import Resolver, gateway, message_meta_from_rfc822
|
||||
|
|
@ -33,26 +34,30 @@ MAX_BODY = 25 * 1024 * 1024
|
|||
|
||||
def cmd_pipe(args, resolver) -> int:
|
||||
raw = sys.stdin.buffer.read()
|
||||
deliveries = gateway(args.recipient, message_meta_from_rfc822(raw), resolver, args.app_url)
|
||||
meta = message_meta_from_rfc822(raw, args.trusted_authserv_id)
|
||||
deliveries = gateway(args.recipient, meta, resolver, args.app_url)
|
||||
for d in deliveries:
|
||||
print(json.dumps(d.__dict__), file=sys.stderr)
|
||||
return EX_TEMPFAIL if any(d.outcome == "tempfail" for d in deliveries) else 0
|
||||
|
||||
|
||||
def webhook_message(body: bytes, ctype: str, query: dict) -> tuple[list[str], dict]:
|
||||
def webhook_message(body: bytes, ctype: str, query: dict,
|
||||
trusted_authserv_id: Optional[str] = None) -> tuple[list[str], dict]:
|
||||
if ctype.startswith("message/rfc822"):
|
||||
return query.get("recipient", []), message_meta_from_rfc822(body)
|
||||
return query.get("recipient", []), message_meta_from_rfc822(body, trusted_authserv_id)
|
||||
data = json.loads(body.decode())
|
||||
recipients = data.pop("recipients", None) or [data.pop("recipient")]
|
||||
meta = {"from": data.get("from"), "to": data.get("to", recipients),
|
||||
"subject": data.get("subject", ""), "message_id": data.get("message_id"),
|
||||
"in_reply_to": data.get("in_reply_to"), "references": data.get("references"),
|
||||
"date": data.get("date"), "auto_submitted": data.get("auto_submitted"),
|
||||
"precedence": data.get("precedence"), "text": data.get("text", "")}
|
||||
"precedence": data.get("precedence"), "text": data.get("text", ""),
|
||||
"authentication": data.get("authentication")} # as verified by the inbound provider
|
||||
return recipients, meta
|
||||
|
||||
|
||||
def make_webhook_server(listen: str, app_url: str, resolver: Resolver) -> ThreadingHTTPServer:
|
||||
def make_webhook_server(listen: str, app_url: str, resolver: Resolver,
|
||||
trusted_authserv_id: Optional[str] = None) -> ThreadingHTTPServer:
|
||||
host, port = listen.rsplit(":", 1)
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
|
|
@ -63,7 +68,8 @@ def make_webhook_server(listen: str, app_url: str, resolver: Resolver) -> Thread
|
|||
body = self.rfile.read(length)
|
||||
try:
|
||||
recipients, meta = webhook_message(body, self.headers.get("Content-Type", ""),
|
||||
parse_qs(urlsplit(self.path).query))
|
||||
parse_qs(urlsplit(self.path).query),
|
||||
trusted_authserv_id)
|
||||
except (ValueError, KeyError) as e:
|
||||
return self._reply(400, {"error": f"bad inbound payload: {e}"})
|
||||
if not recipients:
|
||||
|
|
@ -92,6 +98,9 @@ def main(argv=None) -> int:
|
|||
ap.add_argument("--statute", action="append", metavar="FILE",
|
||||
help="pin a statute from local configuration (keyed by its namespace)")
|
||||
ap.add_argument("--registry", metavar="FILE", help="local registry JSON")
|
||||
ap.add_argument("--trusted-authserv-id", metavar="ID",
|
||||
help="read sender authentication only from Authentication-Results with this id "
|
||||
"(our own MTA); without it, no verdict is reported")
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("pipe")
|
||||
p.add_argument("--recipient", action="append", required=True)
|
||||
|
|
@ -104,7 +113,7 @@ def main(argv=None) -> int:
|
|||
resolver = Resolver.with_statute_files(args.statute, local_registry=registry)
|
||||
if args.cmd == "pipe":
|
||||
return cmd_pipe(args, resolver)
|
||||
srv = make_webhook_server(args.listen, args.app_url, resolver)
|
||||
srv = make_webhook_server(args.listen, args.app_url, resolver, args.trusted_authserv_id)
|
||||
print(f"mas-gateway webhook on http://{args.listen} -> {args.app_url}", file=sys.stderr)
|
||||
srv.serve_forever()
|
||||
return 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue