mail-address-semantics/integrations/exim/NOTES.md
tegwick d766d61e7f MAS-WP-0004: Postfix integration profile, corpus probe in pytest, Exim notes
Profile in integrations/postfix (install.sh + README), now used by the
live deployment. Probe runs every conformance-corpus local-part and the
relay negatives against it; tests/test_postfix_profile.py runs it where
Docker is available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5787@bnt-lap001
Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd
2026-10-19 09:25:03 +02:00

51 lines
2 KiB
Markdown

# Exim notes (MAS-WP-0004 T03)
No Exim profile is shipped, because there is no demand yet. These notes say
what one would need. The ACL finding was checked against the stock config
of Debian bookworm `exim4-daemon-light` (Exim 4.96, 2026-10-19). The router
and transport points come from the Exim documentation and are untested.
## Pitfalls
- **Stock ACL rejects every P2 address.** `acl_check_rcpt` denies local
domains' local-parts that match `CHECK_RCPT_LOCAL_LOCALPARTS`, which
defaults to `^[.] : ^.*[@%!/|`#&?]`. So `ticket!callback@D` and
`ticket=PR%2B42@D` get `550 restricted characters in address`. This is the
same failure class as Postfix's `reject_unauth_destination`. Upstream
Exim's example `configure` has the same rule (`^[.] : ^.*[@%!/|]`).
- **Percent hack** is off by default (`percent_hack_domains` unset), so `%`
is not rewritten once the ACL lets it through. Exim has no bang-path
rewriting.
- **Case.** Routers lower-case `$local_part` unless `caseful_local_part =
true`, which would hide `Ticket=PR42` (invalid in MAS) as `ticket=PR42`.
- **Suffixes.** A router with `local_part_suffix = +*` strips facets before
the transport sees them. Do not set it on the MAS router.
## Settings a profile would need
```
# main section: drop % and ! from the local-domain restriction
CHECK_RCPT_LOCAL_LOCALPARTS = ^[.] : ^.*[@/|`#&?]
# router, first for the MAS domain
mas:
driver = accept
domains = D
caseful_local_part = true
transport = mas_pipe
# transport: one recipient per call, envelope recipient as received
mas_pipe:
driver = pipe
batch_max = 1
user = mas
command = /usr/local/bin/mas-gateway --statute <statute> \
--trusted-authserv-id D pipe --app-url <url> \
--recipient ${quote:$local_part}@$domain
```
Keep `CHECK_RCPT_REMOTE_LOCALPARTS` as it is: relaying stays closed, and only
D is a local domain. Sender authentication would use Exim's built-in
`dkim`/`spf`/`dmarc` ACL conditions with `add_header =
Authentication-Results: D; ...`. Incoming A-R headers for D must be removed
first (`headers_remove`).