%# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC %# %# %# (Except where explicitly superseded by other copyright notices) %# %# %# LICENSE: %# %# This work is made available to you under the terms of Version 2 of %# the GNU General Public License. A copy of that license should have %# been provided with this software, but in any event can be snarfed %# from www.gnu.org. %# %# This work is distributed in the hope that it will be useful, but %# WITHOUT ANY WARRANTY; without even the implied warranty of %# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %# General Public License for more details. %# %# You should have received a copy of the GNU General Public License %# along with this program; if not, write to the Free Software %# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA %# 02110-1301 or visit their web page on the internet at %# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. %# %# %# CONTRIBUTION SUBMISSION POLICY: %# %# (The following paragraph is not intended to limit the rights granted %# to you to modify and distribute this software under the terms of %# the GNU General Public License and is only of importance to you if %# you choose to contribute your changes and enhancements to the %# community by submitting them to Best Practical Solutions, LLC.) %# %# By intentionally submitting any modifications, corrections or %# derivatives to this work, or any other work intended for use with %# Request Tracker, to Best Practical Solutions, LLC, you confirm that %# you are the copyright holder for those contributions and you grant %# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, %# royalty-free, perpetual, license to use, copy, create derivative %# works based on those contributions, and sublicense and distribute %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} <%ARGS> $Message $WarnUnsigned => undef $Reverify => 1 <%INIT> my @runs; my $needs_unsigned_warning = $WarnUnsigned; my @protocols = RT::Crypt->EnabledProtocols; my $re_protocols = join '|', map "\Q$_\E", @protocols; foreach ( $Message->SplitHeaders ) { if ( s/^X-RT-($re_protocols)-Status:\s*//io ) { push @runs, [ $1, RT::Crypt->ParseStatus( Protocol => "$1", Status => $_ ) ]; } $needs_unsigned_warning = 0 if /^X-RT-Incoming-Signature:/; # if this is not set, then the email is generated by RT, and so we don't # need "email is unsigned" warnings $needs_unsigned_warning = 0 if not /^Received:/; } return unless @runs or $needs_unsigned_warning; my $reverify_cb = sub { my $top = shift; my $txn = $top->TransactionObj; unless ( $txn && $txn->id ) { return (0, "Couldn't get transaction of attachment #". $top->id); } my $attachments = $txn->Attachments->Clone; $attachments->Limit( FIELD => 'ContentType', VALUE => 'application/x-rt-original-message' ); my $original = $attachments->First; unless ( $original ) { return (0, "Couldn't find attachment with original email of transaction #". $txn->id); } my $parser = RT::EmailParser->new(); $parser->SmartParseMIMEEntityFromScalar( Message => $original->Content, Decode => 0, Exact => 1, ); my $entity = $parser->Entity; unless ( $entity ) { return (0, "Couldn't parse content of attachment #". $original->id); } my @res = RT::Crypt->VerifyDecrypt( Entity => $entity ); return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted") unless @res; $top->DelHeader("X-RT-$_-Status") for RT::Crypt->Protocols; $top->AddHeader(map { ("X-RT-". $_->{Protocol} ."-Status" => $_->{'status'} ) } @res); $top->DelHeader("X-RT-Privacy"); my %protocols; $protocols{$_->{Protocol}}++ for @res; $top->AddHeader('X-RT-Privacy' => $_ ) for sort keys %protocols; $top->DelHeader('X-RT-Incoming-Signature'); my @status = RT::Crypt->ParseStatus( Protocol => $res[0]{'Protocol'}, Status => $res[0]{'status'}, ); for ( @status ) { if ( $_->{'Operation'} eq 'Verify' && $_->{'Status'} eq 'DONE' ) { $top->AddHeader( 'X-RT-Incoming-Signature' => $_->{'UserString'} ); $needs_unsigned_warning = 0; } } return (1, "Reverified original message"); }; my @messages; foreach my $run ( @runs ) { my $protocol = shift @$run; $protocol = $RT::Crypt::PROTOCOLS{lc $protocol}; foreach my $line ( @$run ) { if ( $line->{'Operation'} eq 'KeyCheck' ) { next unless $Reverify; # if a public key was missing during verification then we want try again next unless $line->{'KeyType'} eq 'public' && $line->{'Status'} eq 'MISSING'; # but only if we have key my %key = RT::Crypt->GetPublicKeyInfo( Protocol => $protocol, Key => $line->{'Key'} ); if ( $key{'info'} ) { my ($status, $msg) = $reverify_cb->($Message); unless ($status) { $RT::Logger->error($msg); } else { return $m->comp('SELF', %ARGS, Reverify => 0); } } else { push @messages, { Tag => $protocol, Classes => [qw/keycheck bad/], Value => $m->interp->apply_escapes( loc( "Public key '0x[_1]' is required to verify signature", $line->{'Key'} ), 'h'), }; } } elsif ( $line->{'Operation'} eq 'PassphraseCheck' ) { next if $line->{'Status'} eq 'DONE'; push @messages, { Tag => $protocol, Classes => ['passphrasecheck', lc $line->{Status}], Value => $m->interp->apply_escapes( loc( $line->{'Message'} ), 'h'), }; } elsif ( $line->{'Operation'} eq 'Decrypt' ) { push @messages, { Tag => $protocol, Classes => ['decrypt', lc $line->{Status}], Value => $m->interp->apply_escapes( loc( $line->{'Message'} ), 'h'), }; } elsif ( $line->{'Operation'} eq 'Verify' ) { push @messages, { Tag => $protocol, Classes => ['verify', lc $line->{Status}, 'trust-'.($line->{Trust} || 'UNKNOWN')], Value => $m->interp->apply_escapes( loc( $line->{'Message'} ), 'h'), }; } else { next if $line->{'Status'} eq 'DONE'; push @messages, { Tag => $protocol, Classes => [lc $line->{Operation}, lc $line->{Status}], Value => $m->interp->apply_escapes( loc( $line->{'Message'} ), 'h'), } } } } push @messages, { Tag => "Signing", Classes => ['verify', 'bad'], Value => loc('Warning! This is NOT signed!') } if $needs_unsigned_warning; return unless @messages; my %seen; @messages = grep !$seen{$_->{Value}}++, @messages; return @messages;