sub _check_valid_signature()

in lib/Mail/SpamAssassin/Plugin/DKIM.pm [1022:1151]


sub _check_valid_signature {
  my($self, $pms, $type, $signatures) = @_;

  my $sig_type = lc $type;
  $self->_get_authors($pms)  if !$pms->{"dkim_author_addresses"};

  my(@valid_signatures);
  my $conf = $pms->{conf};
  # DKIM signatures check
  if ($pms->{"${sig_type}_signatures_ready"}) {
    my $sig_result_supported;
    # dkim_minimum_key_bits is evaluated for ARC signatures as well
    my $minimum_key_bits = $conf->{dkim_minimum_key_bits};
    foreach my $signature (@$signatures) {
      # old versions of Mail::DKIM would give undef for an invalid signature
      next if !defined $signature;
      # test for empty selector (must not treat a selector "0" as missing!)
      next if !defined $signature->selector || $signature->selector eq "";

      my($info, $valid, $expired);
      $valid = $signature->result eq 'pass';
      $info = $valid ? 'VALID' : 'FAILED';
      if ($valid && $signature->UNIVERSAL::can("check_expiration")) {
        $expired = !$signature->check_expiration;
        $info .= ' EXPIRED'  if $expired;
      }
      my $key_size;
      if ($valid && !$expired && $minimum_key_bits) {
        $key_size = eval { my $pk = $signature->get_public_key;
                           $pk && $pk->cork && $pk->cork->size * 8 };
        if ($key_size) {
          $signature->{_spamassassin_key_size} = $key_size; # stash it for later
          $info .= " WEAK($key_size)"  if $key_size < $minimum_key_bits;
        }
      }
      push(@valid_signatures, $signature)  if $valid && !$expired;

      # check if we have a potential Author Domain Signature, valid or not
      my ($d) = (defined $signature->identity)? $signature->identity =~ /\@(\S+)/ : ($signature->domain);
      if (!defined $d) {
        # can be undefined on a broken signature with missing required tags
      } else {
        $d = lc $d;
        if ($pms->{dkim_author_domains}->{$d}) {  # SDID matches author domain
          $pms->{"${sig_type}_has_any_author_sig"}->{$d} = 1;
          if ($valid && !$expired &&
              $key_size && $key_size >= $minimum_key_bits) {
            $pms->{"${sig_type}_has_valid_author_sig"}->{$d} = 1;
          } elsif ( $signature->result_detail
                   =~ /\b(?:timed out|SERVFAIL)\b/i) {
            $pms->{"${sig_type}_author_sig_tempfailed"}->{$d} = 1;
          }
        }
      }
      if ($type eq 'DKIM') {
        if (would_log("dbg","dkim")) {
          dbg("dkim: %s %s, i=%s, d=%s, s=%s, a=%s, c=%s, %s, %s, %s",
            $info,
            $signature->isa('Mail::DKIM::DkSignature') ? 'DK' : 'DKIM',
            map(!defined $_ ? '(undef)' : $_,
              $signature->identity, $d, $signature->selector,
              $signature->algorithm, scalar($signature->canonicalization),
              $key_size ? "key_bits=$key_size" : "unknown key size",
              $signature->result ),
            defined $d && $pms->{dkim_author_domains}->{$d}
              ? 'matches author domain'
              : 'does not match author domain',
          );
        }
      } elsif ($type eq 'ARC') {
        if (would_log("dbg","dkim")) {
          dbg("dkim: %s %s, i=%s, d=%s, s=%s, a=%s, c=%s, %s, %s, %s",
            $info,
            $type,
            map(!defined $_ ? '(undef)' : $_,
              $signature->identity, $d, $signature->selector,
              $signature->algorithm, scalar($signature->canonicalization),
              $key_size ? "key_bits=$key_size" : "unknown key size",
              $signature->result ),
            defined $d && $pms->{dkim_author_domains}->{$d}
              ? 'matches author domain'
              : 'does not match author domain',
          );
        }
      }
    }

    if (@valid_signatures) {
      if ($type eq 'DKIM') {
        $pms->{dkim_signed} = 1;
        $pms->{dkim_valid} = 1;

        # supply values for both tags
        my(%seen1, %seen2, %seen3, @identity_list, @domain_list, @selector_list);
        @identity_list = grep(defined $_ && $_ ne '' && !$seen1{$_}++,
                            map($_->identity, @valid_signatures));
        @domain_list =   grep(defined $_ && $_ ne '' && !$seen2{$_}++,
                            map($_->domain, @valid_signatures));
        @selector_list = grep(defined $_ && $_ ne '' && !$seen3{$_}++,
                            map($_->selector, @valid_signatures));
        $pms->set_tag('DKIMIDENTITY',
                    @identity_list == 1 ? $identity_list[0] : \@identity_list);
        $pms->set_tag('DKIMDOMAIN',
                    @domain_list == 1   ? $domain_list[0]   : \@domain_list);
        $pms->set_tag('DKIMSELECTOR',
                    @selector_list == 1 ? $selector_list[0] : \@selector_list);
      } elsif ($type eq 'ARC') {
        $pms->{arc_signed} = 1;
        $pms->{arc_valid} = 1;
      }
      # let the result stand out more clearly in the log, use uppercase
      my $sig = $valid_signatures[0];
      my $sig_res = $sig->result_detail;
      dbg("dkim: $type signature verification result: %s", uc($sig_res));

    } elsif (@$signatures) {
      if ($type eq 'DKIM') {
        $pms->{dkim_signed} = 1;
      } elsif ($type eq 'ARC') {
        $pms->{arc_signed} = 1;
      }
      my $sig = @$signatures[0];
      my $sig_res = $sig->result_detail;
      dbg("dkim: $type signature verification result: %s", uc($sig_res));

    } else {
      dbg("dkim: $type signature verification result: none");
    }
  }
}