Skip to content

check/milter: apply fail_open to milter session-dial failures - #865

Open
marcominetti wants to merge 1 commit into
foxcpp:devfrom
marcominetti:fix/milter-fail-open-dial-error-ondev
Open

check/milter: apply fail_open to milter session-dial failures#865
marcominetti wants to merge 1 commit into
foxcpp:devfrom
marcominetti:fix/milter-fail-open-dial-error-ondev

Conversation

@marcominetti

Copy link
Copy Markdown

check.milter's fail_open directive is documented to let a message through when the milter can't be consulted, but it was silently ineffective for one specific failure mode: the milter being completely unreachable.

CheckStateForMsg (internal/check/milter/milter.go) dials the milter backend directly via c.cl.Session() and returns any error unconditionally — this happens before a *state exists, so the failure never reaches ioError(), which is the only place fail_open is actually checked. The result: with fail_open set, a message is still hard-rejected (SMTP-level error straight to the sender) if the milter is simply down, exactly the case fail_open exists to guard against.

This PR handles a dial failure the same way ioError() handles a later I/O failure: when fail_open is true, return a sessionless check state with skipChecks: true instead of propagating the raw error. CheckConnection and Close() are guarded for the resulting nil session; CheckSender/CheckRcpt/CheckBody already short-circuit on skipChecks via existing checks, so no change was needed there.

Includes a regression test (TestCheckStateForMsg_DialFailure_FailOpenTrue) that dials a closed loopback port to simulate an unreachable milter without a timeout wait, and confirms it fails against the pre-fix code (verified via mutation check: reverted the fix locally, confirmed this test fails with the exact predicted symptom, reapplied, confirmed it passes).

CheckStateForMsg dials the milter backend directly and returned any
dial error unconditionally, before a *state existed to route the
failure through ioError()'s fail_open handling. This made fail_open
silently ineffective for the specific case of the milter being
unreachable: the original SMTP sender got a hard reject regardless
of the directive.

Handle a dial failure the same way ioError() handles a later I/O
failure: skip checks and let the message through when fail_open is
set, otherwise keep today's behavior and reject. Guard CheckConnection
and Close() for the resulting sessionless state.
@marcominetti

Copy link
Copy Markdown
Author

Filed #866 to track this properly — should have opened it before the PR per CONTRIBUTING.md, doing it now for the record.

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.

1 participant