"Fossies" - the Fresh Open Source Software Archive

Member "Mail-DKIM-0.55/lib/Mail/DKIM/ARC/Verifier.pm" (12 Apr 2019, 26916 Bytes) of package /linux/privat/Mail-DKIM-0.55.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Verifier.pm" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.54_vs_0.55.

    1 #!/usr/bin/perl
    2 
    3 # Copyright 2017 FastMail Pty Ltd.  All Rights Reserved.
    4 # Bron Gondwana <brong@fastmailteam.com>
    5 
    6 # This program is free software; you can redistribute it and/or
    7 # modify it under the same terms as Perl itself.
    8 
    9 use strict;
   10 use warnings;
   11 
   12 =head1 NAME
   13 
   14 Mail::DKIM::ARC::Verifier - verifies an ARC-Sealed message
   15 
   16 =head1 SYNOPSIS
   17 
   18   use Mail::DKIM::ARC::Verifier;
   19 
   20   # create a verifier object
   21   my $arc = Mail::DKIM::ARC::Verifier->new();
   22 
   23   # read an email from a file handle
   24   $arc->load(*STDIN);
   25 
   26   # or read an email and pass it into the verifier, incrementally
   27   while (<STDIN>)
   28   {
   29       # remove local line terminators
   30       chomp;
   31       s/\015$//;
   32 
   33       # use SMTP line terminators
   34       $arc->PRINT("$_\015\012");
   35   }
   36   $arc->CLOSE;
   37 
   38   # what is the result of the verify?
   39   my $result = $arc->result;
   40 
   41   # print the results for all the message-signatures and seals on the message
   42   foreach my $signature ($arc->signatures)
   43   {
   44       print $signature->prefix() . ' v=' . $signature->instance .
   45                                      ' ' . $signature->result_detail . "\n";
   46   }
   47 
   48   # example output.  Note that to pass, only the MOST RECENT ARC-Message-Signature
   49   # must match, because other steps may have modified the signature.  What matters
   50   # is that all ARC-Seals pass, and the most recent ARC-Message-Signature passes.
   51 
   52 =cut
   53 
   54 =head1 DESCRIPTION
   55 
   56 The verifier object allows an email message to be scanned for ARC
   57 seals and their associated signatures to be verified. The verifier
   58 tracks the state of the message as it is read into memory. When the
   59 message has been completely read, the signatures are verified and the
   60 results of the verification can be accessed.
   61 
   62 To use the verifier, first create the verifier object. Then start
   63 "feeding" it the email message to be verified. When all the _headers_
   64 have been read, the verifier:
   65 
   66  1. checks whether any ARC signatures were found
   67  2. queries for the public keys needed to verify the signatures
   68  3. sets up the appropriate algorithms and canonicalization objects
   69  4. canonicalizes the headers and computes the header hash
   70 
   71 Then, when the _body_ of the message has been completely fed into the
   72 verifier, the body hash is computed and the signatures are verified.
   73 
   74 The results of the verification can be checked with L</"result()">
   75 or L</"signatures()">.
   76 
   77 The final result is calculated by the algorithm layed out in
   78 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-06 -
   79 if ALL ARC-Seal headers pass and the highest index (i=)
   80 ARC-Message-Signature passes, then the seal is intact.
   81 
   82 =head1 CONSTRUCTOR
   83 
   84 =head2 new()
   85 
   86 Constructs an object-oriented verifier.
   87 
   88   my $arc = Mail::DKIM::ARC::Verifier->new();
   89 
   90   my $arc = Mail::DKIM::ARC::Verifier->new(%options);
   91 
   92 The only options supported at this time are:
   93 
   94 =over
   95 
   96 =item AS_Canonicalization
   97 
   98 if specified, the canonicalized message for the ARC-Seal
   99 is written to the referenced string or file handle.
  100 
  101 =item AMA_Canonicalization
  102 
  103 if specified, the canonicalized message for the ARC-Message-Signature
  104 is written to the referenced string or file handle.
  105 
  106 =item Strict
  107 
  108 If true, rejects sha1 hashes and signing keys shorter than 1024 bits.
  109 
  110 =back
  111 
  112 =cut
  113 
  114 package Mail::DKIM::ARC::Verifier;
  115 use base 'Mail::DKIM::Common';
  116 use Mail::DKIM::ARC::MessageSignature;
  117 use Mail::DKIM::ARC::Seal;
  118 use Mail::Address;
  119 use Carp;
  120 our $VERSION                   = 0.55;
  121 our $MAX_SIGNATURES_TO_PROCESS = 50;
  122 
  123 sub init {
  124     my $self = shift;
  125     $self->SUPER::init;
  126     $self->{signatures} = [];
  127     $self->{result}     = undef;    # we're done once this is set
  128 }
  129 
  130 # @{$arc->{signatures}}
  131 #   array of L<Mail::DKIM::ARC::{Signature|Seal}> objects, representing all
  132 #   parseable message signatures and seals found in the header,
  133 #   ordered from the top of the header to the bottom.
  134 #
  135 # $arc->{signature_reject_reason}
  136 #   simple string listing a reason, if any, for not using a signature.
  137 #   This may be a helpful diagnostic if there is a signature in the header,
  138 #   but was found not to be valid. It will be ambiguous if there are more
  139 #   than one signatures that could not be used.
  140 #
  141 # @{$arc->{headers}}
  142 #   array of strings, each member is one header, in its original format.
  143 #
  144 # $arc->{algorithms}
  145 #   array of algorithms, one for each signature being verified.
  146 #
  147 # $arc->{result}
  148 #   string; the result of the verification (see the result() method)
  149 #
  150 
  151 sub handle_header {
  152     my $self = shift;
  153     my ( $field_name, $contents, $line ) = @_;
  154 
  155     $self->SUPER::handle_header( $field_name, $contents );
  156 
  157     if ( lc($field_name) eq 'arc-message-signature' ) {
  158         eval {
  159             my $signature = Mail::DKIM::ARC::MessageSignature->parse($line);
  160             $self->add_signature($signature);
  161         };
  162         if ($@) {
  163 
  164             # the only reason an error should be thrown is if the
  165             # signature really is unparse-able
  166 
  167             # otherwise, invalid signatures are caught in finish_header()
  168 
  169             chomp( my $E = $@ );
  170             $self->{signature_reject_reason} = $E;
  171         }
  172     }
  173 
  174     if ( lc($field_name) eq 'arc-seal' ) {
  175         eval {
  176             my $signature = Mail::DKIM::ARC::Seal->parse($line);
  177             $self->add_signature($signature);
  178         };
  179         if ($@) {
  180 
  181             # the only reason an error should be thrown is if the
  182             # signature really is unparse-able
  183 
  184             # otherwise, invalid signatures are caught in finish_header()
  185 
  186             chomp( my $E = $@ );
  187             $self->{signature_reject_reason} = $E;
  188         }
  189     }
  190 
  191 }
  192 
  193 sub add_signature {
  194     my ( $self, $signature ) = @_;
  195     croak 'wrong number of arguments' unless ( @_ == 2 );
  196 
  197     return if $self->{result};    # already failed
  198 
  199     push @{ $self->{signatures} }, $signature;
  200 
  201     unless ( $self->check_signature($signature) ) {
  202         $signature->result( 'invalid', $self->{signature_reject_reason} );
  203         return;
  204     }
  205 
  206     # signature looks ok, go ahead and query for the public key
  207     $signature->fetch_public_key;
  208 
  209     # create a canonicalization filter and algorithm
  210     my $algorithm_class =
  211       $signature->get_algorithm_class( $signature->algorithm );
  212     my $algorithm = $algorithm_class->new(
  213         Signature              => $signature,
  214         Debug_Canonicalization => $signature->isa('Mail::DKIM::ARC::Seal')
  215         ? $self->{AS_Canonicalization}
  216         : $self->{AMS_Canonicalization},
  217     );
  218 
  219     # push through the headers parsed prior to the signature header
  220     if ( $algorithm->wants_pre_signature_headers ) {
  221 
  222         # Note: this will include the signature header that led to this
  223         # "algorithm"...
  224         foreach my $head ( @{ $self->{headers} } ) {
  225             $algorithm->add_header($head);
  226         }
  227     }
  228 
  229     # save the algorithm
  230     $self->{algorithms} ||= [];
  231     push @{ $self->{algorithms} }, $algorithm;
  232 
  233  # check for bogus tags (should be done much earlier but better late than never)
  234  # tagkeys is uniq'd via a hash, rawtaglen counts all the tags
  235     my @tagkeys   = keys %{ $signature->{tags_by_name} };
  236     my $rawtaglen = $#{ $signature->{tags} };
  237 
  238     # crock: ignore empty clause after trailing semicolon
  239     $rawtaglen--
  240       if $signature->{tags}->[ $#{ $signature->{tags} } ]->{raw} =~ /^\s*$/;
  241 
  242     # duplicate tags
  243     if ( $rawtaglen != $#tagkeys ) {
  244         $self->{result}  = 'fail';                         # bogus
  245         $self->{details} = 'Duplicate tag in signature';
  246         return;
  247     }
  248 
  249     # invalid tag name
  250     if ( grep { !m{[a-z][a-z0-9_]*}i } @tagkeys ) {
  251         $self->{result}  = 'fail';                         # bogus
  252         $self->{details} = 'Invalid tag in signature';
  253         return;
  254     }
  255 
  256     if ( $signature->isa('Mail::DKIM::ARC::Seal') ) {
  257         my ($instance);
  258         $instance = $signature->instance() || '';
  259 
  260         if ( $instance !~ m{^\d+$} or $instance < 1 or $instance > 1024 ) {
  261             $self->{result}  = 'fail';                                   # bogus
  262             $self->{details} = sprintf "Invalid ARC-Seal instance '%s'",
  263               $instance;
  264             return;
  265         }
  266 
  267         if ( $self->{seals}[$instance] ) {
  268             $self->{result} = 'fail';                                    # dup
  269             if ( $signature eq $self->{seals}[$instance] ) {
  270                 $self->{details} = sprintf 'Duplicate ARC-Seal %d', $instance;
  271             }
  272             else {
  273                 $self->{details} = sprintf 'Redundant ARC-Seal %d', $instance;
  274             }
  275             return;
  276         }
  277 
  278         $self->{seals}[$instance] = $signature;
  279     }
  280     elsif ( $signature->isa('Mail::DKIM::ARC::MessageSignature') ) {
  281         my $instance = $signature->instance() || '';
  282 
  283         if ( $instance !~ m{^\d+$} or $instance < 1 or $instance > 1024 ) {
  284             $self->{result} = 'fail';    # bogus
  285             $self->{details} =
  286               sprintf "Invalid ARC-Message-Signature instance '%s'", $instance;
  287             return;
  288         }
  289 
  290         if ( $self->{messages}[$instance] ) {
  291             $self->{result} = 'fail';    # dup
  292             if ( $signature->as_string() eq
  293                 $self->{messages}[$instance]->as_string() )
  294             {
  295                 $self->{details} = sprintf 'Duplicate ARC-Message-Signature %d',
  296                   $instance;
  297             }
  298             else {
  299                 $self->{details} = sprintf 'Redundant ARC-Message-Signature %d',
  300                   $instance;
  301             }
  302             return;
  303         }
  304         $self->{messages}[$instance] = $signature;
  305     }
  306 }
  307 
  308 sub check_signature {
  309     my $self = shift;
  310     croak 'wrong number of arguments' unless ( @_ == 1 );
  311     my ($signature) = @_;
  312 
  313     unless ( $signature->check_version ) {
  314 
  315         # unsupported version
  316         if ( defined $signature->version ) {
  317             $self->{signature_reject_reason} =
  318               'unsupported version ' . $signature->version;
  319         }
  320         else {
  321             $self->{signature_reject_reason} = 'missing v tag';
  322         }
  323         return 0;
  324     }
  325 
  326     unless ( $signature->algorithm
  327         && $signature->get_algorithm_class( $signature->algorithm )
  328         && ( !$self->{Strict} || $signature->algorithm ne 'rsa-sha1' )
  329       )    # no more SHA1 for us in strict mode
  330     {
  331         # unsupported algorithm
  332         $self->{signature_reject_reason} = 'unsupported algorithm';
  333         if ( defined $signature->algorithm ) {
  334             $self->{signature_reject_reason} .= ' ' . $signature->algorithm;
  335         }
  336         return 0;
  337     }
  338 
  339     unless ( $signature->check_canonicalization ) {
  340 
  341         # unsupported canonicalization method
  342         $self->{signature_reject_reason} = 'unsupported canonicalization';
  343         if ( defined $signature->canonicalization ) {
  344             $self->{signature_reject_reason} .=
  345               ' ' . $signature->canonicalization;
  346         }
  347         return 0;
  348     }
  349 
  350     unless ( $signature->check_protocol ) {
  351 
  352         # unsupported query protocol
  353         $self->{signature_reject_reason} =
  354           !defined( $signature->protocol )
  355           ? 'missing q tag'
  356           : 'unsupported query protocol, q=' . $signature->protocol;
  357         return 0;
  358     }
  359 
  360     unless ( $signature->check_expiration ) {
  361 
  362         # signature has expired
  363         $self->{signature_reject_reason} = 'signature is expired';
  364         return 0;
  365     }
  366 
  367     unless ( defined $signature->domain ) {
  368 
  369         # no domain specified
  370         $self->{signature_reject_reason} = 'missing d tag';
  371         return 0;
  372     }
  373 
  374     if ( $signature->domain eq '' ) {
  375 
  376         # blank domain
  377         $self->{signature_reject_reason} = 'invalid domain in d tag';
  378         return 0;
  379     }
  380 
  381     unless ( defined $signature->selector ) {
  382 
  383         # no selector specified
  384         $self->{signature_reject_reason} = 'missing s tag';
  385         return 0;
  386     }
  387 
  388     return 1;
  389 }
  390 
  391 sub check_public_key {
  392     my $self = shift;
  393     croak 'wrong number of arguments' unless ( @_ == 2 );
  394     my ( $signature, $public_key ) = @_;
  395 
  396     my $result = 0;
  397     eval {
  398         $@ = undef;
  399 
  400         # HACK- I'm indecisive here about whether I want the
  401         # check_foo functions to return false or to "die"
  402         # on failure
  403 
  404         # check public key's allowed hash algorithms
  405         $result =
  406           $public_key->check_hash_algorithm( $signature->hash_algorithm );
  407 
  408 # HACK- DomainKeys signatures are allowed to have an empty g=
  409 # tag in the public key
  410 #        my $empty_g_means_wildcard = $signature->isa('Mail::DKIM::DkSignature');
  411 
  412         # check public key's granularity
  413         $result &&= $public_key->check_granularity( $signature->instance, 0 );
  414 
  415         #                $signature->instance, $empty_g_means_wildcard);
  416 
  417         die $@ if $@;
  418     };
  419     if ($@) {
  420         my $E = $@;
  421         chomp $E;
  422         $self->{signature_reject_reason} = "public key: $E";
  423     }
  424     return $result;
  425 }
  426 
  427 #
  428 # called when the verifier has received the last of the message headers
  429 # (body is still to come)
  430 #
  431 sub finish_header {
  432     my $self = shift;
  433 
  434     # Signatures we found and were successfully parsed are stored in
  435     # $self->{signatures}. If none were found, our result is "none".
  436 
  437     if ( @{ $self->{signatures} } == 0
  438         && !defined( $self->{signature_reject_reason} ) )
  439     {
  440         $self->{result} = 'none';
  441         return;
  442     }
  443 
  444     # check for duplicate AAR headers (dup AS and AMS checked in add_signature)
  445     my @aars = [];
  446     foreach my $hdr ( @{ $self->{headers} } ) {
  447         if ( my ($i) = $hdr =~ m{ARC-Authentication-Results:\s*i=(\d+)\s*;}i ) {
  448             if ( defined $aars[$i] ) {
  449                 $self->{result} = 'fail';
  450                 $self->{details} =
  451                   "Duplicate ARC-Authentication-Results header $1";
  452                 return;
  453             }
  454             $aars[$i] = $hdr;
  455         }
  456     }
  457 
  458     foreach my $algorithm ( @{ $self->{algorithms} } ) {
  459         $algorithm->finish_header(
  460             Headers => $self->{headers},
  461             Chain   => 'pass'
  462         );
  463     }
  464 
  465     # stop processing signatures that are already known to be invalid
  466     @{ $self->{algorithms} } = grep {
  467         my $sig = $_->signature;
  468         !( $sig->result && $sig->result eq 'invalid' );
  469     } @{ $self->{algorithms} };
  470 
  471     if (   @{ $self->{algorithms} } == 0
  472         && @{ $self->{signatures} } > 0 )
  473     {
  474         $self->{result} = $self->{signatures}->[0]->result || 'invalid';
  475         $self->{details} = $self->{signatures}->[0]->{verify_details}
  476           || $self->{signature_reject_reason};
  477         return;
  478     }
  479 }
  480 
  481 sub _check_and_verify_signature {
  482     my $self = shift;
  483     my ($algorithm) = @_;
  484 
  485     # check signature
  486     my $signature = $algorithm->signature;
  487 
  488     if ( not $signature->get_tag('d') ) {    # All sigs must have a D tag
  489         $self->{signature_reject_reason} = 'missing D tag';
  490         return ( 'fail', $self->{signature_reject_reason} );
  491     }
  492 
  493     if ( not $signature->get_tag('b') ) {    # All sigs must have a B tag
  494         $self->{signature_reject_reason} = 'missing B tag';
  495         return ( 'fail', $self->{signature_reject_reason} );
  496     }
  497 
  498     if ( not $signature->isa('Mail::DKIM::ARC::Seal') ) {    # AMS tests
  499         unless ( $signature->get_tag('bh') ) {    # AMS must have a BH tag
  500             $self->{signature_reject_reason} = 'missing BH tag';
  501             return ( 'fail', $self->{signature_reject_reason} );
  502         }
  503         if ( ( $signature->get_tag('h') || '' ) =~ /arc-seal/i )
  504         {                                         # cannot cover AS
  505             $self->{signature_reject_reason} =
  506               'Arc-Message-Signature covers Arc-Seal';
  507             return ( 'fail', $self->{signature_reject_reason} );
  508         }
  509     }
  510 
  511     # AMS signature must not
  512 
  513     # get public key
  514     my $pkey;
  515     eval { $pkey = $signature->get_public_key; };
  516     if ($@) {
  517         my $E = $@;
  518         chomp $E;
  519         $self->{signature_reject_reason} = "public key: $E";
  520         return ( 'invalid', $self->{signature_reject_reason} );
  521     }
  522 
  523     unless ( $self->check_public_key( $signature, $pkey ) ) {
  524         return ( 'invalid', $self->{signature_reject_reason} );
  525     }
  526 
  527     # make sure key is big enough
  528     my $keysize = $pkey->cork->size * 8;    # in bits
  529     if ( $keysize < 1024 && $self->{Strict} ) {
  530         $self->{signature_reject_reason} = "Key length $keysize too short";
  531         return ( 'fail', $self->{signature_reject_reason} );
  532     }
  533 
  534     # verify signature
  535     my $result;
  536     my $details;
  537     local $@ = undef;
  538     eval {
  539         $result = $algorithm->verify() ? 'pass' : 'fail';
  540         $details = $algorithm->{verification_details} || $@;
  541     };
  542     if ($@) {
  543 
  544         # see also add_signature
  545         chomp( my $E = $@ );
  546         if ( $E =~ /(OpenSSL error: .*?) at / ) {
  547             $E = $1;
  548         }
  549         elsif ( $E =~ /^(panic:.*?) at / ) {
  550             $E = "OpenSSL $1";
  551         }
  552         $result  = 'fail';
  553         $details = $E;
  554     }
  555     return ( $result, $details );
  556 }
  557 
  558 sub finish_body {
  559     my $self = shift;
  560 
  561     return if $self->{result};    # already failed
  562 
  563     foreach my $algorithm ( @{ $self->{algorithms} } ) {
  564 
  565         # finish canonicalizing
  566         $algorithm->finish_body;
  567 
  568         my ( $result, $details ) =
  569           $self->_check_and_verify_signature($algorithm);
  570 
  571         # save the results of this signature verification
  572         $algorithm->{result}  = $result;
  573         $algorithm->{details} = $details;
  574         $self->{signature} ||= $algorithm->signature;    # something if we fail
  575         $algorithm->signature->result( $result, $details );
  576     }
  577 
  578     my $seals    = $self->{seals}    || [];
  579     my $messages = $self->{messages} || [];
  580     unless ( @$seals or @$messages ) {
  581         $self->{result}  = 'none';
  582         $self->{details} = 'no ARC headers found';
  583         return;
  584     }
  585 
  586     # determine if it's valid:
  587     # 5.1.1.5.  Determining the 'cv' Tag Value for ARC-Seal
  588 
  589     #    In order for a series of ARC sets to be considered valid, the
  590     #    following statements MUST be satisfied:
  591 
  592     #    1.  The chain of ARC sets must have structural integrity (no sets or
  593     #        set component header fields missing, no duplicates, excessive
  594     #        hops (cf.  Section 5.1.1.1.1), etc.);
  595 
  596     if ( $#$seals == 0 ) {
  597         $self->{result}  = 'fail';
  598         $self->{details} = 'missing ARC-Seal 1';
  599         return;
  600     }
  601     if ( $#$messages == 0 ) {
  602         $self->{result}  = 'fail';
  603         $self->{details} = 'missing ARC-Message-Signature 1';
  604         return;
  605     }
  606 
  607     if ( $#$messages > $#$seals ) {
  608         $self->{result}  = 'fail';
  609         $self->{details} = 'missing Arc-Seal ' . $#$messages;
  610         return;
  611     }
  612 
  613     foreach my $i ( 1 .. $#$seals ) {
  614 
  615 # XXX - we should error if it's already present, but that's done above if at all
  616         if ( !$seals->[$i] ) {
  617             $self->{result}  = 'fail';
  618             $self->{details} = "missing ARC-Seal $i";
  619             return;
  620         }
  621         if ( !$messages->[$i] ) {
  622             $self->{result}  = 'fail';
  623             $self->{details} = "missing ARC-Message-Signature $i";
  624             return;
  625         }
  626     }
  627 
  628     # 2. All ARC-Seal header fields MUST validate;
  629     foreach my $i ( 1 .. $#$seals ) {
  630         my $result = $seals->[$i]->result();
  631         if ( $result ne 'pass' ) {
  632             $self->{signature} = $seals->[$i]->signature;
  633             $self->{result}    = $result;
  634             $self->{details}   = $seals->[$i]->result_detail();
  635             return;
  636         }
  637     }
  638 
  639     #    3.  All ARC-Seal header fields MUST have a chain value (cv=) status
  640     #        of "pass" (except the first which MUST be "none"); and
  641     my $cv = $seals->[1]->get_tag('cv');
  642     if ( !defined $cv or $cv ne 'none' ) {
  643         $self->{signature} = $seals->[1]->signature;
  644         $self->{result}    = 'fail';
  645         $self->{details}   = 'first ARC-Seal must be cv=none';
  646         return;
  647     }
  648     foreach my $i ( 2 .. $#$seals ) {
  649         my $cv = $seals->[$i]->get_tag('cv');
  650         if ( $cv ne 'pass' ) {
  651             $self->{signature} = $seals->[$i]->signature;
  652             $self->{result}    = 'fail';
  653             $self->{details}   = "wrong cv for ARC-Seal i=$i";
  654             return;
  655         }
  656     }
  657 
  658     #    4.  The newest (highest instance number (i=)) AMS header field MUST
  659     #        validate.
  660     my $result = $messages->[$#$seals]->result();
  661     if ( $result ne 'pass' ) {
  662         $self->{signature} = $messages->[$#$seals]->signature;
  663         $self->{result}    = $result;
  664         $self->{details}   = $messages->[$#$seals]->result_detail();
  665         return;
  666     }
  667 
  668     # Success!
  669     $self->{signature} = $seals->[$#$seals]->signature();
  670     $self->{result}    = 'pass';
  671     $self->{details}   = $seals->[$#$seals]->result_detail();
  672 }
  673 
  674 sub result_detail {
  675     my $self = shift;
  676 
  677     return 'none' if $self->{result} eq 'none';
  678 
  679     my @items;
  680     foreach my $signature ( @{ $self->{signatures} } ) {
  681         my $type =
  682             ref($signature) eq 'Mail::DKIM::ARC::Seal'             ? 'as'
  683           : ref($signature) eq 'Mail::DKIM::ARC::MessageSignature' ? 'ams'
  684           :   ref($signature);
  685         push @items,
  686             "$type."
  687           . ( $signature->instance()      || '' ) . '.'
  688           . ( $signature->domain()        || '(none)' ) . '='
  689           . ( $signature->result_detail() || '?' );
  690     }
  691 
  692     return $self->{result} . ' (' . join( ', ', @items ) . ')';
  693 }
  694 
  695 =head1 METHODS
  696 
  697 =head2 PRINT()
  698 
  699 Feeds part of the message to the verifier.
  700 
  701   $arc->PRINT("a line of the message\015\012");
  702   $arc->PRINT('more of');
  703   $arc->PRINT(" the message\015\012bye\015\012");
  704 
  705 Feeds content of the message being verified into the verifier.
  706 The API is designed this way so that the entire message does NOT need
  707 to be read into memory at once.
  708 
  709 Please note that although the PRINT() method expects you to use
  710 SMTP-style line termination characters, you should NOT use the
  711 SMTP-style dot-stuffing technique described in RFC 2821 section 4.5.2.
  712 Nor should you use a <CR><LF>.<CR><LF> sequence to terminate the
  713 message.
  714 
  715 =head2 CLOSE()
  716 
  717 Call this when finished feeding in the message.
  718 
  719   $arc->CLOSE;
  720 
  721 This method finishes the canonicalization process, computes a hash,
  722 and verifies the signature.
  723 
  724 =head2 load()
  725 
  726 Load the entire message from a file handle.
  727 
  728   $arc->load($file_handle);
  729 
  730 Reads a complete message from the designated file handle,
  731 feeding it into the verifier. The message must use <CRLF> line
  732 terminators (same as the SMTP protocol).
  733 
  734 =head2 message_originator()
  735 
  736 Access the "From" header.
  737 
  738   my $address = $arc->message_originator;
  739 
  740 Returns the "originator address" found in the message, as a
  741 L<Mail::Address> object.
  742 This is typically the (first) name and email address found in the
  743 From: header. If there is no From: header,
  744 then an empty L<Mail::Address> object is returned.
  745 
  746 To get just the email address part, do:
  747 
  748   my $email = $arc->message_originator->address;
  749 
  750 See also L</"message_sender()">.
  751 
  752 =head2 message_sender()
  753 
  754 Access the "From" or "Sender" header.
  755 
  756   my $address = $arc->message_sender;
  757 
  758 Returns the "sender" found in the message, as a L<Mail::Address> object.
  759 This is typically the (first) name and email address found in the
  760 Sender: header. If there is no Sender: header, it is the first name and
  761 email address in the From: header. If neither header is present,
  762 then an empty L<Mail::Address> object is returned.
  763 
  764 To get just the email address part, do:
  765 
  766   my $email = $arc->message_sender->address;
  767 
  768 The "sender" is the mailbox of the agent responsible for the actual
  769 transmission of the message. For example, if a secretary were to send a
  770 message for another person, the "sender" would be the secretary and
  771 the "originator" would be the actual author.
  772 
  773 =head2 result()
  774 
  775 Access the result of the verification.
  776 
  777   my $result = $arc->result;
  778 
  779 Gives the result of the verification. The following values are possible:
  780 
  781 =over
  782 
  783 =item pass
  784 
  785 Returned if a valid ARC chain was found, with all the ARC-Seals passing,
  786 and the most recent (highest index) ARC-Message-Signature passing.
  787 
  788 =item fail
  789 
  790 Returned if any ARC-Seal failed, or if the ARC-Message-Signature failed.
  791 Will also be a fail if there is a DNS temporary failure, which is a
  792 known flaw in this version of the ARC::Verifier.  Future versions may
  793 reject this message outright (4xx) and ask the sender to attempt
  794 delivery later to avoid creating a broken chain.  There is no temperror
  795 for ARC, as it doesn't make sense to sign a chain with temperror in it
  796 or every spammer would just use one of those.
  797 
  798 =item invalid
  799 
  800 Returned if a ARC-Seal could not be checked because of a problem
  801 in the signature itself or the public key record. I.e. the signature
  802 could not be processed.
  803 
  804 =item none
  805 
  806 Returned if no ARC-* headers were found.
  807 
  808 =back
  809 
  810 =cut
  811 
  812 =head2 result_detail()
  813 
  814 Access the result, plus details if available.
  815 
  816   my $detail = $dkim->result_detail;
  817 
  818 The detail is constructed by taking the result (e.g. "pass", "fail",
  819 "invalid" or "none") and appending any details provided by the verification
  820 process for the topmost ARC-Seal in parenthesis.
  821 
  822 The following are possible results from the result_detail() method:
  823 
  824   pass
  825   fail (bad RSA signature)
  826   fail (OpenSSL error: ...)
  827   fail (message has been altered)
  828   fail (body has been altered)
  829   invalid (bad instance)
  830   invalid (invalid domain in d tag)
  831   invalid (missing q tag)
  832   invalid (missing d tag)
  833   invalid (missing s tag)
  834   invalid (unsupported version 0.1)
  835   invalid (unsupported algorithm ...)
  836   invalid (unsupported canonicalization ...)
  837   invalid (unsupported query protocol ...)
  838   invalid (signature is expired)
  839   invalid (public key: not available)
  840   invalid (public key: unknown query type ...)
  841   invalid (public key: syntax error)
  842   invalid (public key: unsupported version)
  843   invalid (public key: unsupported key type)
  844   invalid (public key: missing p= tag)
  845   invalid (public key: invalid data)
  846   invalid (public key: does not support email)
  847   invalid (public key: does not support hash algorithm 'sha1')
  848   invalid (public key: does not support signing subdomains)
  849   invalid (public key: revoked)
  850   invalid (public key: granularity mismatch)
  851   invalid (public key: granularity is empty)
  852   invalid (public key: OpenSSL error: ...)
  853   none
  854 
  855 =head2 signatures()
  856 
  857 Access all of this message's signatures.
  858 
  859   my @all_signatures = $arc->signatures;
  860 
  861 Use $signature->result or $signature->result_detail to access
  862 the verification results of each signature.
  863 
  864 Use $signature->instance and $signature->prefix to find the
  865 instance and header-name for each signature.
  866 =cut
  867 
  868 sub signatures {
  869     my $self = shift;
  870     croak 'unexpected argument' if @_;
  871 
  872     return @{ $self->{signatures} };
  873 }
  874 
  875 =head1 AUTHOR
  876 
  877 Bron Gondwana, E<lt>brong@fastmailteam.comE<gt>
  878 
  879 =head1 COPYRIGHT AND LICENSE
  880 
  881 Copyright (C) 2017 FastMail Pty Ltd.
  882 
  883 This library is free software; you can redistribute it and/or modify
  884 it under the same terms as Perl itself, either Perl version 5.8.6 or,
  885 at your option, any later version of Perl 5 you may have available.
  886 
  887 =cut
  888 
  889 1;