NAME

Mail::DKIM2::Gate - decide whether a front end may sign a message

SYNOPSIS

my $g = Mail::DKIM2::Gate->check($message,
    PubkeyCallback => $cb, SkipTimestampCheck => 0,
    AllowNullBodyRecipe => 0);
unless ($g->{ok}) { warn "not signing: $g->{message}" }

DESCRIPTION

The gate shared by bin/dkim2sign, bin/dkim2-milter and the authentication_milter handler Mail::Milter::Authentication::Handler::DKIM2Sign. Mail::DKIM2::Signer checks only what it needs to sign -- it refuses a chain it cannot number (a DKIM2-Signature with no usable i=, an i= or m= above MAX_CHAIN_LENGTH, a duplicate) but does not verify the upstream signatures or the Message-Instance chain. A front end that extends a DKIM2 chain should first make sure the chain is worth extending, which is what this module decides.

check

Mail::DKIM2::Gate->check($message, %opts) takes the whole message as a string (CRLF line endings) and returns a hashref. Options: PubkeyCallback, Resolver and SkipTimestampCheck are passed to the Verifier, IgnorePrefixes to both the Verifier and the Message-Instance chain check (the caller's own header fields, hashed by neither end), AllowNullBodyRecipe permits an unsigned Message-Instance whose body Recipe is null (see "Null body Recipes"), SigningDomain is the d= the caller will sign with: when the top upstream signature carries nd= the gate passes only if it equals that domain (case-insensitive) and refuses otherwise ("top signature nd=X names another domain"); without it a top nd= is refused as before. VerifyResult supplies an already-computed verifier result so the upstream signatures are not verified again.

The upstream DKIM2-Signatures are verified with a Verifier that allows an unsigned Message-Instance above the top signature (the one the caller is about to sign), and the Message-Instance chain must match the content and undo cleanly down to m=1, including the header history below a null body Recipe.

The result has ok (true to sign), verify_result (none without a DKIM2-Signature), has_chain, top_null (the top Message-Instance has a null body Recipe), covered_m (the highest m= of any DKIM2-Signature with a valid, positive-integer i=, or 0: those instances, 1 to covered_m, are signed upstream; a signature without a valid i= never counts, and the Verifier reports it as a permerror), top_signed (the top Message-Instance is among them), unsigned_null (the m= of the highest Message-Instance above covered_m with a null body Recipe -- the top or one under it -- or 0) and, when refusing, reason (upstream-chain, broken-mi-chain or null-body-recipe) and a human-readable message. top_signed, covered_m and unsigned_null are new in 0.16; top_null is as in 0.15.

Null body Recipes

A null body Recipe ("b": null) says the previous body cannot be recreated. A DKIM2-Signature with m=k covers Message-Instances 1 to k, so every instance above the highest m= of the valid upstream signatures is unsigned: whoever signs next is the first to vouch for it. The gate refuses (null-body-recipe) when any of those unsigned instances has a null body Recipe -- the top one, or one with another unsigned instance added over it. That is a null this hop is introducing, typically a list manager that rewrote the body and added an unsigned instance for the outbound signer to sign. Signing it is the host's choice, made with AllowNullBodyRecipe.

A null that already arrived signed (a list host's post, forwarded unchanged, whether or not a later hop added an ordinary instance over it) was declared and signed by the upstream domain; the gate extends it without the option. Either way the upstream signatures must verify and the header history below the null must check out.