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) or a filehandle (or an object with getline), and returns a hashref. A filehandle is read a piece at a time, its body going straight into the Verifier, so the message is never held whole. 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), PolicyCallback is passed to the Verifier too, AllowNullBodyRecipe is deprecated and does nothing (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 Message-Instance chain still is.

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. Every From the signed instances have had must be Authentic (-09 section 11); a From changed in an instance no upstream signature covers is not checked here but by Mail::DKIM2::Signer, against the d= it signs with.

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 or broken-mi-chain; null-body-recipe is no longer given since 0.29) 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, and a verifier reports a message carrying one as a PERMERROR (-09 sections 13 and 14.2: "has a null recipe"). 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, and this hop's own: typically a list manager that rewrote the body beyond what a Recipe can record and added an unsigned instance for the outbound signer.

Since 0.29 the gate passes such a null: the message will not verify downstream whatever this hop does, but signing records and vouches for the header changes this hop made. AllowNullBodyRecipe, which used to be needed for that, is deprecated and does nothing.

A null that arrived already signed fails the upstream verification, and is refused as upstream-chain like any other broken upstream chain. The header history below this hop's own null must still check out.