[Rt-commit] rt branch, 4.2/smime, repushed

Alex Vandiver alexmv at bestpractical.com
Thu Aug 29 12:49:55 EDT 2013


The branch 4.2/smime was deleted and repushed:
       was e702e73564a20d6b3da2cb9b333765877c314831
       now ead85becddcd4a5170ec56c0e198da6ac5ecb55d

162:  4c2fe9c !   1:  81da587 Process Sign/Encrypt values later on update
    @@ -1,17 +1,13 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    processes Sign/Encrypt values later on update
    +    Process Sign/Encrypt values later on update
         
    -    Undef value is indication of "do whatever queue's options say".
    -    Code in Interface::Web was changed to fix some bug when value is not
    -    a boolean but an array (d3341b787e3a734c441596b84ce621339f327851).
    -    This was correct thing to do, but in a wrong place.
    -    
    -    Also, we don't need Encode now as we just turn value into ascii
    -    scalar without UTF8 flag.
    -    
    -    The bug shouldn't affect system unless admin wants to hide GnuPG
    -    widget that allows people to change crypt preferences on reply.
    +    A value for Sign or Encrypt of undef is an indication of "do whatever
    +    the queue's options say."  d3341b7 resolved a bug of inserting array
    +    references into X-RT-Sign, but in doing so lost the possibility to pass
    +    undef into ProcessUpdateMessage, and have the queue default kick in.  In
    +    practice, this is only relevant if the signing controls were not
    +    submitted, but customizations may do so.
     
     diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
     --- a/lib/RT/Interface/Web.pm
  1:  106495d !   2:  ac0d3a1 Refactor code which calls GPG::Interface to elimate duplicate code
    @@ -14,10 +14,6 @@
      #            ...
      #        );
      
    -+=head2 CallGnuPG
    -+
    -+=cut
    -+
     +sub CallGnuPG {
     +    my %args = (
     +        Options     => undef,
  2:  ce5881a < ---:  ------- Factor out code which finalizes GnuPG output into a data structure
  3:  42a5f38 !   3:  bcb1f89 Generalize CallGnuPG slightly more, allowing more code reuse
    @@ -69,7 +69,7 @@
     -                print { $handle{'command'} } "y\n";
     +    return CallGnuPG(
     +        Method      => "--delete-secret-and-public-key",
    -+        CommandArgs => [\--', $key],
    ++        CommandArgs => ["--", $key],
     +        Callback    => sub {
     +            my %handle = @_;
     +            while ( my $str = readline $handle{'status'} ) {
    @@ -83,7 +83,20 @@
     -    my $err = $@;
     -    close $handle{'stdout'};
     -
    --    return Finalize( $err, \%handle );
    +-    my %res;
    +-    $res{'exit_code'} = $?;
    +-    foreach ( qw(stderr logger status) ) {
    +-        $res{$_} = do { local $/ = undef; readline $handle{$_} };
    +-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
    +-        close $handle{$_};
    +-    }
    +-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
    +-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
    +-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
    +-    if ( $err || $res{'exit_code'} ) {
    +-        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
    +-    }
    +-    return %res;
     +        },
     +    );
      }
  4:  51ae5fd =   4:  b0dba81 Minor cleanups to Probe, the one remaining non-CallGnuPG gpg interaction
  5:  9f5f01c !   5:  0da7281 Catch errors on close()
    @@ -24,7 +24,7 @@
     +        }
          }
      
    -     return Finalize( $err, \%handle );
    +     my %res;
     @@
          foreach ( qw(stderr logger status) ) {
              $res{$_} = do { local $/ = undef; readline $handle{$_} };
  6:  333b2b7 =   6:  80c0798 Rename Key to Signer for clarity and consistency
  7:  72edb8b !   7:  1194665 Rename Method to Command for clarity; "--foo" is not a "method"
    @@ -146,7 +146,7 @@
          return CallGnuPG(
     -        Method      => "--delete-secret-and-public-key",
     +        Command     => "--delete-secret-and-public-key",
    -         CommandArgs => [\--', $key],
    +         CommandArgs => ["--", $key],
              Callback    => sub {
                  my %handle = @_;
     @@
  8:  76e3e68 !   8:  079e3e1 Only set the default key if we actually have one
    @@ -1,6 +1,6 @@
     Author: Alex Vandiver <alexmv at bestpractical.com>
     
    -    Only set the defualt key if we actually have one
    +    Only set the default key if we actually have one
     
     diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
     --- a/lib/RT/Crypt/GnuPG.pm
  9:  e92c425 =   9:  8938e19 Only set the passphrase if we have one
 10:  992b410 !  10:  312cb6e Split IO::Handle::CRLF into its own file in RT::Crypt::GnuPG::CRLFHandle
    @@ -51,6 +51,54 @@
     --- /dev/null
     +++ b/lib/RT/Crypt/GnuPG/CRLFHandle.pm
     @@
    ++# BEGIN BPS TAGGED BLOCK {{{
    ++#
    ++# COPYRIGHT:
    ++#
    ++# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
    ++#                                          <sales at bestpractical.com>
    ++#
    ++# (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 }}}
    ++
     +package RT::Crypt::GnuPG::CRLFHandle;
     +use strict;
     +use warnings;
 11:  2db4d8b < ---:  ------- make pseudo OO API out of functions, it was always the plan
 12:  8503800 < ---:  ------- first pass on generic Crypt API for RT
 13:  36343b6 < ---:  ------- switch SignEncrypt to new API
---:  ------- >  11:  09d54d2 Switch out RT::Crypt::GnuPG function calls for class methods
---:  ------- >  12:  6b99048 Create a generic RT::Crypt class to dispatch methods from
---:  ------- >  13:  6d2312e Move data-storage UseKeyFor... methods onto RT::Crypt
---:  ------- >  14:  aad107d Provide a RT::Crypt->LoadImplementation method to load RT::Crypt::...
---:  ------- >  15:  30ab442 Add RT::Crypt->Protocols, to return the supported encryption protocols
---:  ------- >  16:  3ffc9fd Make protocol loading case-insensitive
---:  ------- >  17:  217e572 Add a role for encryption classes
---:  ------- >  18:  57c195c Move GetPassphrase onto the role
---:  ------- >  19:  967cfef Move SignEncrypt to dispatch from RT::Crypt
161:  a2bf5f9 !  20:  d0ecfdc Do not error if no From address is provided
    @@ -1,11 +1,10 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    avoid scrip death when correspond address is not set
    +    Do not error if no From address is provided
         
    -    It's misconfiguration when correspond address is not
    -    set in the config and for a queue, but still stack
    -    trace from Crypt.pm into scrip and further is not
    -    helpful.
    +    If the correspond address is neither set globally nor on the queue, the
    +    From adress will be empty.  While this is a misconfiguration, avoid
    +    erroring out when attempting to guess the signing address.
     
     diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
     --- a/lib/RT/Crypt.pm
    @@ -32,7 +31,7 @@
     +use warnings;
     +
     +use RT::Test::GnuPG
    -+  tests         => 5,
    ++  tests         => 6,
     +  gnupg_options => {
     +    passphrase    => 'rt-test',
     +    'trust-model' => 'always',
    @@ -43,13 +42,14 @@
     +{
     +    $queue = RT::Test->load_or_create_queue(
     +        Name => 'Regression',
    -+        Sign => 1,
    ++        SignAuto => 1,
     +    );
     +    ok $queue && $queue->id, 'loaded or created queue';
     +    ok !$queue->CorrespondAddress, 'address not set';
     +}
     +
    -+{
    ++use Test::Warn;
    ++warnings_like {
     +    my $ticket = RT::Ticket->new( RT->SystemUser );
     +    my ($status, undef, $msg) = $ticket->Create(
     +        Queue => $queue->id,
    @@ -61,6 +61,6 @@
     +    my $log = RT::Test->file_content([RT::Test->temp_directory, 'rt.debug.log']);
     +    like $log, qr{secret key not available}, 'error in the log';
     +    unlike $log, qr{Scrip .*? died}m, "scrip didn't die";
    -+}
    ++} [qr{gpg: keyring .*? created}];
     +
     
 14:  adc53ba !  21:  b3a3025 Turn FindProtectedParts into a two-step process
    @@ -1,43 +1,97 @@
    -Author: Ruslan Zakirov <ruz at bestpractical.com>
    +Author: Alex Vandiver <alexmv at bestpractical.com>
     
    -    re-write FindProtectedParts
    +    Turn FindProtectedParts into a two-step process
         
    -    we have to split this functionality into two separate steps:
    -    1) CheckIfProtected
    -    2) FindScatteredParts
    +    In order to simplify and genericize detection of encrypted or signed
    +    messages, the monolithic FindProtectedParts has been split into three
    +    pieces:
    +    
    +      CheckIfProtected - Determines if the particular entity (and its
    +        children) is a signed or encrypted component.  This deals with
    +        RFC3156 'multipart/signed' or 'multipart/encrypted' messages.
    +    
    +      FindScatteredParts - Passed all unclaimed non-multipart parts, and
    +        examines them as a whole; it looks for detached signatures at a
    +        distance, or inline PGP messages.
    +    
    +      FindProtectedParts - Calls the above two in series.  This method is
    +        not tied to GnuPG encryption in any way.
     
     diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
     --- a/lib/RT/Crypt/GnuPG.pm
     +++ b/lib/RT/Crypt/GnuPG.pm
     @@
    -     return %res;
    - }
    - 
    --sub FindProtectedParts {
    + 
    + sub FindProtectedParts {
    +     my $self = shift;
     -    my %args = ( Entity => undef, CheckBody => 1, @_ );
    -+sub CheckIfProtected {
    -+    my $self = shift;
    -+    my %args = ( Entity => undef, @_ );
    ++    my %args = (
    ++        Entity => undef,
    ++        Skip => {},
    ++        Scattered => 1,
    ++        @_
    ++    );
     +
          my $entity = $args{'Entity'};
    ++    return () if $args{'Skip'}{ $entity };
      
     -    # inline PGP block, only in singlepart
     -    unless ( $entity->is_multipart ) {
     -        my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
    --
    ++    my %info = $self->CheckIfProtected( Entity => $entity );
    ++    if (keys %info) {
    ++        $args{'Skip'}{ $entity } = 1;
    ++        return \%info;
    ++    }
    + 
     -        my $io = $entity->open('r');
     -        unless ( $io ) {
     -            $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
     -            return ();
     -        }
    -+    # we check inline PGP block later in another sub
    -+    return () unless $entity->is_multipart;
    - 
    -         # Deal with "partitioned" PGP mail, which (contrary to common
    -         # sense) unnecessarily applies a base64 transfer encoding to PGP
    -@@
    ++    my @res;
    + 
    +-        # Deal with "partitioned" PGP mail, which (contrary to common
    +-        # sense) unnecessarily applies a base64 transfer encoding to PGP
    +-        # mail (whose content is already base64-encoded).
    +-        if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) {
    +-            my $decoder = MIME::Decoder->new( $entity->head->mime_encoding );
    +-            if ($decoder) {
    +-                local $@;
    +-                eval {
    +-                    my $buf = '';
    +-                    open my $fh, '>', \$buf
    +-                        or die "Couldn't open scalar for writing: $!";
    +-                    binmode $fh, ":raw";
    +-                    $decoder->decode($io, $fh);
    +-                    close $fh or die "Couldn't close scalar: $!";
    +-
    +-                    open $fh, '<', \$buf
    +-                        or die "Couldn't re-open scalar for reading: $!";
    +-                    binmode $fh, ":raw";
    +-                    $io = $fh;
    +-                    1;
    +-                } or do {
    +-                    $RT::Logger->error("Couldn't decode body: $@");
    +-                }
    ++    # not protected itself, look inside
    ++    push @res, $self->FindProtectedParts(
    ++        %args, Entity => $_, Scattered => 0,
    ++    ) foreach grep !$args{'Skip'}{$_}, $entity->parts;
    ++
    ++    if ( $args{'Scattered'} ) {
    ++        my %parent;
    ++        my $filter; $filter = sub {
    ++            $parent{$_[0]} = $_[1];
    ++            unless ( $_[0]->is_multipart ) {
    ++                return () if $args{'Skip'}{$_[0]};
    ++                return $_[0];
                  }
    -         }
    +-        }
    ++            return map $filter->($_, $_[0]), grep !$args{'Skip'}{$_}, $_[0]->parts;
    ++        };
    ++        my @parts = $filter->($entity);
    ++        return @res unless @parts;
      
     -        while ( defined($_ = $io->getline) ) {
     -            next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
    @@ -48,22 +102,39 @@
     -                Format  => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
     -                Data    => $entity,
     -            };
    --        }
    ++        my @list = $self->FindScatteredParts( Parts => \@parts, Parents => \%parent, Skip => $args{'Skip'} );
    ++        if (@list) {
    ++            push @res, @list;
    ++            @parts = grep !$args{'Skip'}{$_}, @parts;
    +         }
     -        $io->close;
    -+
    -+    # RFC3156, multipart/{signed,encrypted}
    -+    my $type = $entity->effective_type;
    -+    return () unless $type = =~ /^multipart\/(?:encrypted|signed)$/;
    -+
    -+    unless ( $entity->parts == 2 ) {
    -+        $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
    -         return ();
    +-        return ();
          }
      
    --    # RFC3156, multipart/{signed,encrypted}
    ++    return @res;
    ++}
    ++
    ++sub CheckIfProtected {
    ++    my $self = shift;
    ++    my %args = ( Entity => undef, @_ );
    ++
    ++    my $entity = $args{'Entity'};
    ++
    ++    # we check inline PGP block later in another sub
    ++    return () unless $entity->is_multipart;
    ++
    +     # RFC3156, multipart/{signed,encrypted}
     -    if ( ( my $type = $entity->effective_type ) =~ /^multipart\/(?:encrypted|signed)$/ ) {
     -        unless ( $entity->parts == 2 ) {
     -            $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
    ++    my $type = $entity->effective_type;
    ++    return () unless $type =~ /^multipart\/(?:encrypted|signed)$/;
    ++
    ++    unless ( $entity->parts == 2 ) {
    ++        $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
    ++        return ();
    ++    }
    ++
     +    my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' );
     +    unless ( $protocol ) {
     +        $RT::Logger->error( "Entity is '$type', but has no protocol defined. Skipped" );
    @@ -104,18 +175,19 @@
     +    return ();
     +}
     +
    ++
     +sub FindScatteredParts {
     +    my $self = shift;
     +    my %args = ( Parts => [], Skip => {}, @_ );
    ++
    ++    my @res;
    ++
    ++    my @parts = @{ $args{'Parts'} };
      
     -        if ( $type eq 'multipart/encrypted' ) {
     -            unless ( $protocol eq 'application/pgp-encrypted' ) {
     -                $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" );
     -                return ();
    -+    my @res;
    -+
    -+    my @parts = @{ $args{'Parts'} };
    -+
     +    # attachments signed with signature in another part
     +    {
     +        my @file_indices;
    @@ -187,9 +259,19 @@
     -        }
     -        elsif ( $fname =~ /\.sig$/i && $part->effective_type eq 'application/octet-stream' ) {
     -            push @file_indices, $i;
    --        }
    --    }
     +            $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
    ++
    ++            $args{'Skip'}{$data_part_in} = 1;
    ++            $args{'Skip'}{$sig_part} = 1;
    ++            push @res, {
    ++                Type      => 'signed',
    ++                Format    => 'Attachment',
    ++                Top       => $args{'Parents'}{$data_part_in},
    ++                Data      => $data_part_in,
    ++                Signature => $sig_part,
    ++            };
    +         }
    +     }
      
     -    my (@res, %skip);
     -    foreach my $i ( @file_indices ) {
    @@ -205,30 +287,34 @@
     -        unless ( defined $data_part_idx ) {
     -            $RT::Logger->error("Found $sig_name attachment, but didn't find $file_name");
     -            next;
    -+            $args{'Skip'}{$data_part_in} = 1;
    -+            $args{'Skip'}{$sig_part} = 1;
    -+            push @res, {
    -+                Type      => 'signed',
    -+                Format    => 'Attachment',
    -+                Top       => $entity,
    -+                Data      => $data_part_in,
    -+                Signature => $sig_part,
    -+            };
    -         }
    +-        }
     -        my $data_part_in = $entity->parts($data_part_idx);
    --
    ++    # attachments with inline encryption
    ++    foreach my $part ( @parts ) {
    ++        next if $args{'Skip'}{$part};
    ++
    ++        my $fname = $part->head->recommended_filename || '';
    ++        next unless $fname =~ /\.${RE_FILE_EXTENSIONS}$/;
    + 
     -        $skip{"$data_part_in"}++;
     -        $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
    --        push @res, {
    ++        $RT::Logger->debug("Found encrypted attachment '$fname'");
    ++
    ++        $args{'Skip'}{$part} = 1;
    +         push @res, {
     -            Type      => 'signed',
     -            Format    => 'Attachment',
     -            Top       => $entity,
     -            Data      => $data_part_in,
     -            Signature => $sig_part,
    --        };
    ++            Type    => 'encrypted',
    ++            Format  => 'Attachment',
    ++            Top     => $args{'Parents'}{$part},
    ++            Data    => $part,
    +         };
          }
      
    -     # attachments with inline encryption
    +-    # attachments with inline encryption
     -    my @encrypted_indices =
     -        grep {($entity->parts($_)->head->recommended_filename || '') =~ /\.${RE_FILE_EXTENSIONS}$/}
     -            0 .. $entity->parts - 1;
    @@ -237,44 +323,30 @@
     -        my $part = $entity->parts($i);
     -        $skip{"$part"}++;
     -        $RT::Logger->debug("Found encrypted attachment '". $part->head->recommended_filename ."'");
    ++    # inline PGP block
     +    foreach my $part ( @parts ) {
     +        next if $args{'Skip'}{$part};
     +
    -+        my $fname = $part->head->recommended_filename || '';
    -+        next unless $fname =~ /\.${RE_FILE_EXTENSIONS}$/;
    -+
    -+        $RT::Logger->debug("Found encrypted attachment '$fname'");
    ++        my $type = $self->_CheckIfProtectedInline( $part );
    ++        next unless $type;
    ++
    ++        my $file = ($part->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
     +
     +        $args{'Skip'}{$part} = 1;
              push @res, {
     -            Type      => 'encrypted',
     -            Format    => 'Attachment',
    -+            Type    => 'encrypted',
    -+            Format  => 'Attachment',
    -             Top     => $entity,
    -             Data    => $part,
    +-            Top     => $entity,
    +-            Data    => $part,
    ++            Type      => $type,
    ++            Format    => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
    ++            Data      => $part,
              };
          }
      
    --    push @res, FindProtectedParts( Entity => $_ )
    +-    push @res, $self->FindProtectedParts( Entity => $_ )
     -        foreach grep !$skip{"$_"}, $entity->parts;
    -+    # inline PGP block
    -+    foreach my $part ( @parts ) {
    -+        next if $args{'Skip'}{$part};
    -+
    -+        my $type = $self->_CheckIfProtectedInline( $entity );
    -+        next unless $type;
    -+
    -+        my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
    -+
    -+        $args{'Skip'}{$part} = 1;
    -+        push @res, {
    -+            Type      => $type,
    -+            Format    => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
    -+            Data      => $entity,
    -+        };
    -+    }
    - 
    +-
          return @res;
      }
      
    @@ -287,6 +359,33 @@
     +        $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
     +        return '';
     +    }
    ++
    ++    # Deal with "partitioned" PGP mail, which (contrary to common
    ++    # sense) unnecessarily applies a base64 transfer encoding to PGP
    ++    # mail (whose content is already base64-encoded).
    ++    if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) {
    ++        my $decoder = MIME::Decoder->new( $entity->head->mime_encoding );
    ++        if ($decoder) {
    ++            local $@;
    ++            eval {
    ++                my $buf = '';
    ++                open my $fh, '>', \$buf
    ++                    or die "Couldn't open scalar for writing: $!";
    ++                binmode $fh, ":raw";
    ++                $decoder->decode($io, $fh);
    ++                close $fh or die "Couldn't close scalar: $!";
    ++
    ++                open $fh, '<', \$buf
    ++                    or die "Couldn't re-open scalar for reading: $!";
    ++                binmode $fh, ":raw";
    ++                $io = $fh;
    ++                1;
    ++            } or do {
    ++                $RT::Logger->error("Couldn't decode body: $@");
    ++            }
    ++        }
    ++    }
    ++
     +    while ( defined($_ = $io->getline) ) {
     +        next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
     +        return $1? 'signed': 'encrypted';
 15:  d42a1a1 < ---:  ------- VerifyDecrypt works with one part only now
 16:  c7bd48b < ---:  ------- make sure Auth::Crypt is used as mail plugin
 17:  3027b9a < ---:  ------- Auth::Crypt mail plugin out of Auth::GnuPG
---:  ------- >  22:  d24d92e Move FindProtectedParts into RT::Crypt
 30:  8a81cae !  23:  621a397 Unclaimed multipart/signed or multipart/encrypted parts should be skipped
    @@ -1,6 +1,10 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    don't recurse into multipart/{signed,encrypted} messages
    +    Unclaimed multipart/signed or multipart/encrypted parts should be skipped
    +    
    +    If the initial CheckIfProtected calls do not claim a multipart/signed or
    +    multipart/encrypted part, prune its sub-parts from being passed to
    +    FindScatteredParts.
     
     diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
     --- a/lib/RT/Crypt.pm
    @@ -16,9 +20,7 @@
     +        return ();
     +    }
     +
    -+    my @res;
    -+
    +     my @res;
    + 
          # not protected itself, look inside
    -     push @res, $self->FindProtectedParts(
    -         %args, Entity => $_, Scattered => 0,
     
 33:  18776f1 !  24:  23c6432 Assume multipart/{signed,encrypted} parts may be GPG-encrypted
    @@ -1,6 +1,9 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    handle multipart/{singned,encrypted} without protocol set
    +    Assume multipart/{signed,encrypted} parts may be GPG-encrypted
    +    
    +    If the protocol is unset on a multipart/signed or multipart/encrypted
    +    part, assume that it may be GPG and check for GPG inline markers.
     
     diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
     --- a/lib/RT/Crypt/GnuPG.pm
    @@ -51,8 +54,8 @@
          my $io = $entity->open('r');
          unless ( $io ) {
     @@
    -         return '';
          }
    + 
          while ( defined($_ = $io->getline) ) {
     -        next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
     -        return $1? 'signed': 'encrypted';
    @@ -66,3 +69,37 @@
          $io->close;
          return '';
     
    +diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
    +--- a/t/mail/crypt-gnupg.t
    ++++ b/t/mail/crypt-gnupg.t
    +@@
    +         qw/data gnupg keyrings/ );
    + }
    + 
    +-use RT::Test::GnuPG tests => 96, gnupg_options => { homedir => $homedir };
    ++use RT::Test::GnuPG tests => 100, gnupg_options => { homedir => $homedir };
    + use Test::Warn;
    + 
    ++use_ok('RT::Crypt');
    + use_ok('MIME::Entity');
    + 
    + diag 'only signing. correct passphrase';
    +@@
    +     $entity->head->mime_attr( 'Content-Type.protocol' => undef );
    + 
    +     my @parts;
    +-    warning_like {
    +-        @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
    +-    } qr{Entity is 'multipart/encrypted', but has no protocol defined. Skipped};
    +-
    +-    is( scalar @parts, 0, 'no protected parts' );
    ++    warning_like { @parts = RT::Crypt->FindProtectedParts( Entity => $entity ) }
    ++        qr{Entity is 'multipart/encrypted', but has no protocol defined. Checking for PGP part};
    ++    is( scalar @parts, 1, 'one protected part' );
    ++    is( $parts[0]->{'Type'}, 'encrypted', "have encrypted part" );
    ++    is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
    ++    is( $parts[0]->{'Top'}, $entity, "it's the same entity" );
    + }
    + 
    + diag 'wrong signed/encrypted parts: not enought parts';
    +
---:  ------- >  25:  cf8ee45 Move VerifyDecrypt to dispatch from RT::Crypt
---:  ------- >  26:  acdfefb Fix which part is labelled "Top" in signature attachments
---:  ------- >  27:  9dca0e0 Remove Top argument from where it is not needed
---:  ------- >  28:  f24b69f Merge two identical AddStatus/SetStatus blocks into one
---:  ------- >  29:  24f5c2a Remove unused Detach argument
---:  ------- >  30:  8c935dc Remove unnecessary passing of SetStatus to VerifyRFC3156
---:  ------- >  31:  bd32d0a Remove AddStatus/SetStatus arguments to VerifyDecrypt
---:  ------- >  32:  e5db507 Move status header setting into RT::Crypt
---:  ------- >  33:  73d06cf Move alteration of Top component into Verify/Decrypt methods
---:  ------- >  34:  5a31c00 Move ParseStatus to dispatch from RT::Crypt
---:  ------- >  35:  084345c Move key retrieval to dispatch from RT::Crypt
---:  ------- >  36:  a397c66 Refactor UseKeyFor* and GetKeysFor* for generic use
---:  ------- >  37:  3522a04 Add Protocol information to GetPublicKeyInfo call
 74:  a33f42b !  38:  f0a5142 Move DrySign into RT::Crypt
    @@ -1,18 +1,26 @@
    -Author: Ruslan Zakirov <ruz at bestpractical.com>
    +Author: Alex Vandiver <alexmv at bestpractical.com>
     
    -    move DrySign into Crypt::Base
    +    Move DrySign into RT::Crypt
     
    -diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
    ---- a/lib/RT/Crypt/Base.pm
    -+++ b/lib/RT/Crypt/Base.pm
    +diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
    +--- a/lib/RT/Crypt.pm
    ++++ b/lib/RT/Crypt.pm
     @@
    -     return (exit_code => 1, status => []);
    +     return %res;
      }
      
    ++=head2 DrySign Signer => KEY
    ++
    ++Signs a small message with the key, to make sure the key exists and we
    ++have a useable passphrase. The Signer argument MUST be a key identifier
    ++of the signer: either email address, key id or finger print.
    ++
    ++Returns a true value if all went well.
    ++
    ++=cut
    ++
     +sub DrySign {
     +    my $self = shift;
    -+    my %args = ( Signer => undef, @_ );
    -+    my $from = $args{'Signer'};
     +
     +    my $mime = MIME::Entity->build(
     +        Type    => "text/plain",
    @@ -23,30 +31,39 @@
     +    );
     +
     +    my %res = $self->SignEncrypt(
    ++        @_,
     +        Sign    => 1,
     +        Encrypt => 0,
     +        Entity  => $mime,
    -+        Signer  => $from,
     +    );
     +
     +    return $res{exit_code} == 0;
     +}
     +
    - sub CheckIfProtected { return () }
    + =head2 VerifyDecrypt Entity => ENTITY [, Passphrase => undef ]
      
    - sub FindScatteredParts { return () }
    + Locates all protected parts of the L<MIME::Entity> object C<ENTITY>, as
     
     diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
     --- a/lib/RT/Crypt/GnuPG.pm
     +++ b/lib/RT/Crypt/GnuPG.pm
     @@
    +     );
    + }
      
    - =cut
    - 
    +-=head2 KEY
    +-
    +-Signs a small message with the key, to make sure the key exists and 
    +-we have a useable passphrase. The first argument MUST be a key identifier
    +-of the signer: either email address, key id or finger print.
    +-
    +-Returns a true value if all went well.
    +-
    +-=cut
    +-
     -sub DrySign {
     -    my $self = shift;
    --    my %args = ( Signer => undef, @_ );
    --    my $from = $args{'Signer'};
    +-    my $from = shift;
     -
     -    my $mime = MIME::Entity->build(
     -        Type    => "text/plain",
    @@ -72,3 +89,16 @@
      
      This routine returns true if RT's GnuPG support is configured and working 
     
    +diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
    +--- a/share/html/Elements/GnuPG/SignEncryptWidget
    ++++ b/share/html/Elements/GnuPG/SignEncryptWidget
    +@@
    +     if ($address ne $private and $address ne $queue) {
    +         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
    +         $checks_failure = 1;
    +-    } elsif ( not RT::Crypt::GnuPG->DrySign( $address ) ) {
    ++    } elsif ( not RT::Crypt->DrySign( Signer => $address ) ) {
    +         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
    +         $checks_failure = 1;
    +     } else {
    +
 67:  9003549 !  39:  b65c6eb move ParseDate method into RT::Crypt::Role to allow re-use
    @@ -1,33 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    move ParseDate method upper so SMIME can re-use it
    -
    -diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
    ---- a/lib/RT/Crypt/Base.pm
    -+++ b/lib/RT/Crypt/Base.pm
    -@@
    -     );
    - }
    - 
    -+sub ParseDate {
    -+    my $self = shift;
    -+    my $value = shift;
    -+
    -+    # never
    -+    return $value unless $value;
    -+
    -+    require RT::Date;
    -+    my $obj = RT::Date->new( RT->SystemUser );
    -+    # unix time
    -+    if ( $value =~ /^\d+$/ ) {
    -+        $obj->Set( Value => $value );
    -+    } else {
    -+        $obj->Set( Format => 'unknown', Value => $value, Timezone => 'utc' );
    -+    }
    -+    return $obj;
    -+}
    -+
    - 1;
    +    move ParseDate method into RT::Crypt::Role to allow re-use
     
     diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
     --- a/lib/RT/Crypt/GnuPG.pm
    @@ -55,7 +28,7 @@
                  @info{ qw(Trust Created Expire String) }
                      = (split /:/, $line)[0,4,5,8];
     -            $info{ $_ } = _ParseDate( $info{ $_ } )
    -+            $info{'Validity'}{ $_ } = $self->ParseDate( $info{ $_ } )
    ++            $info{ $_ } = $self->ParseDate( $info{ $_ } )
                      foreach qw(Created Expire);
                  push @{ $res[-1]{'User'} ||= [] }, \%info;
              }
    @@ -83,3 +56,38 @@
          my $self = shift;
          my $key = shift;
     
    +diff --git a/lib/RT/Crypt/Role.pm b/lib/RT/Crypt/Role.pm
    +--- a/lib/RT/Crypt/Role.pm
    ++++ b/lib/RT/Crypt/Role.pm
    +@@
    + 
    + requires 'GetKeysForSigning';
    + 
    ++=head2 ParseDate STRING
    ++
    ++Takes a string, and parses and returns a L<RT::Date>; if the string is
    ++purely numeric, assumes is a epoch timestamp.
    ++
    ++=cut
    ++
    ++sub ParseDate {
    ++    my $self = shift;
    ++    my $value = shift;
    ++
    ++    # never
    ++    return $value unless $value;
    ++
    ++    require RT::Date;
    ++    my $obj = RT::Date->new( RT->SystemUser );
    ++    # unix time
    ++    if ( $value =~ /^\d+$/ ) {
    ++        $obj->Set( Value => $value );
    ++    } else {
    ++        $obj->Set( Format => 'unknown', Value => $value, Timezone => 'utc' );
    ++    }
    ++    return $obj;
    ++}
    ++
    ++
    + 1;
    +
 65:  fbd755b !  40:  30beaa8 Move CheckRecipients into RT::Crypt
    @@ -1,13 +1,13 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    move CheckRecipients into RT::Crypt
    +    Move CheckRecipients into RT::Crypt
     
     diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
     --- a/lib/RT/Crypt.pm
     +++ b/lib/RT/Crypt.pm
     @@
    -     return ();
    - } }
    + 
    + =cut
      
     +sub CheckRecipients {
     +    my $self = shift;
    @@ -77,7 +77,7 @@
     +
      sub GetKeysForEncryption {
          my $self = shift;
    -     my %args = @_%2? (Recipient => @_) : (Protocol => undef, For => undef, @_ );
    +     my %args = @_%2? (Recipient => @_) : (Protocol => undef, Recipient => undef, @_ );
     
     diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
     --- a/lib/RT/Crypt/GnuPG.pm
    @@ -156,3 +156,16 @@
          my $self = shift;
          my %args = (
     
    +diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
    +--- a/share/html/Elements/GnuPG/SignEncryptWidget
    ++++ b/share/html/Elements/GnuPG/SignEncryptWidget
    +@@
    +         keys %$self
    +     );
    + 
    +-    my ($status, @issues) = RT::Crypt::GnuPG->CheckRecipients( @recipients );
    ++    my ($status, @issues) = RT::Crypt->CheckRecipients( @recipients );
    +     push @{ $self->{'GnuPGRecipientsKeyIssues'} ||= [] }, @issues;
    +     $checks_failure = 1 unless $status;
    + }
    +
 18:  1f2bdcf !  41:  86d3d32 Move logic from RT::Interface::Email::Auth::GnuPG into ::Crypt
    @@ -1,44 +1,288 @@
    -Author: Ruslan Zakirov <ruz at bestpractical.com>
    +Author: Alex Vandiver <alexmv at bestpractical.com>
     
    -    shrink lib/RT/Interface/Email/Auth/Crypt.pm to shortcut
    +    Move logic from RT::Interface::Email::Auth::GnuPG into ::Crypt
     
     diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
    ---- a/lib/RT/Interface/Email/Auth/Crypt.pm
    +new file mode 100644
    +--- /dev/null
     +++ b/lib/RT/Interface/Email/Auth/Crypt.pm
     @@
    -         Entity => $args{'Message'}, AddStatus => 1,
    -     );
    -     if ( $status && !@res ) {
    --        $args{'Message'}->head->add(
    ++# BEGIN BPS TAGGED BLOCK {{{
    ++#
    ++# COPYRIGHT:
    ++#
    ++# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
    ++#                                          <sales at bestpractical.com>
    ++#
    ++# (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 }}}
    ++
    ++package RT::Interface::Email::Auth::Crypt;
    ++
    ++use strict;
    ++use warnings;
    ++
    ++=head1 NAME
    ++
    ++RT::Interface::Email::Auth::Crypt - decrypting and verifying protected emails
    ++
    ++=head2 DESCRIPTION
    ++
    ++This mail plugin decrypts and verifies incoming emails. Supported
    ++encryption protocols are GnuPG and SMIME.
    ++
    ++This code is independant from code that encrypts/sign outgoing emails, so
    ++it's possible to decrypt data without bringing in encryption. To enable
    ++it put the module in the mail plugins list:
    ++
    ++    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filters...);
    ++
    ++=head3 GnuPG
    ++
    ++To use the gnupg-secured mail gateway, you need to do the following:
    ++
    ++Set up a GnuPG key directory with a pubring containing only the keys
    ++you care about and specify the following in your SiteConfig.pm
    ++
    ++    Set(%GnuPGOptions, homedir => '/opt/rt4/var/data/GnuPG');
    ++
    ++Read also: L<RT::Crypt> and L<RT::Crypt::GnuPG>.
    ++
    ++=head3 SMIME
    ++
    ++Read also: L<RT::Crypt> and L<RT::Crypt::SMIME>.
    ++
    ++=cut
    ++
    ++sub ApplyBeforeDecode { return 1 }
    ++
    ++use RT::Crypt;
    ++use RT::EmailParser ();
    ++
    ++sub GetCurrentUser {
    ++    my %args = (
    ++        Message       => undef,
    ++        RawMessageRef => undef,
    ++        @_
    ++    );
    ++
    ++    foreach my $p ( $args{'Message'}->parts_DFS ) {
    ++        $p->head->delete($_) for qw(
    ++            X-RT-GnuPG-Status X-RT-Incoming-Encryption
    ++            X-RT-Incoming-Signature X-RT-Privacy
    ++            X-RT-Sign X-RT-Encrypt
    ++        );
    ++    }
    ++
    ++    my $msg = $args{'Message'}->dup;
    ++
    ++    my ($status, @res) = VerifyDecrypt(
    ++        Entity => $args{'Message'},
    ++    );
    ++    if ( $status && !@res ) {
     +        $args{'Message'}->head->replace(
    -             'X-RT-Incoming-Encryption' => 'Not encrypted'
    -         );
    -         return 1;
    -@@
    -                     $decrypted = 1;
    -                 }
    -                 if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
    --                    $part->head->add(
    ++            'X-RT-Incoming-Encryption' => 'Not encrypted'
    ++        );
    ++
    ++        return 1;
    ++    }
    ++
    ++    # FIXME: Check if the message is encrypted to the address of
    ++    # _this_ queue. send rejecting mail otherwise.
    ++
    ++    unless ( $status ) {
    ++        $RT::Logger->error("Had a problem during decrypting and verifying");
    ++        my $reject = HandleErrors( Message => $args{'Message'}, Result => \@res );
    ++        return (0, 'rejected because of problems during decrypting and verifying')
    ++            if $reject;
    ++    }
    ++
    ++    # attach the original encrypted message
    ++    $args{'Message'}->attach(
    ++        Type        => 'application/x-rt-original-message',
    ++        Disposition => 'inline',
    ++        Data        => ${ $args{'RawMessageRef'} },
    ++    );
    ++
    ++    $args{'Message'}->head->replace( 'X-RT-Privacy' => 'PGP' );
    ++
    ++    foreach my $part ( $args{'Message'}->parts_DFS ) {
    ++        my $decrypted;
    ++
    ++        my $status = $part->head->get( 'X-RT-GnuPG-Status' );
    ++        if ( $status ) {
    ++            for ( RT::Crypt::GnuPG->ParseStatus( $status ) ) {
    ++                if ( $_->{Operation} eq 'Decrypt' && $_->{Status} eq 'DONE' ) {
    ++                    $decrypted = 1;
    ++                }
    ++                if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
     +                    $part->head->replace(
    -                         'X-RT-Incoming-Signature' => $_->{UserString}
    -                     );
    -                 }
    -             }
    -         }
    - 
    --        $part->head->add(
    ++                        'X-RT-Incoming-Signature' => $_->{UserString}
    ++                    );
    ++                }
    ++            }
    ++        }
    ++
     +        $part->head->replace(
    -             'X-RT-Incoming-Encryption' => 
    -                 $decrypted ? 'Success' : 'Not encrypted'
    -         );
    -     }
    - 
    -     my %seen;
    --    $args{'Message'}->head->add( 'X-RT-Privacy' => $_ )
    -+    $args{'Message'}->head->replace( 'X-RT-Privacy' => $_ )
    -         foreach grep !$seen{$_}++, @found;
    - 
    -     return 1;
    ++            'X-RT-Incoming-Encryption' => 
    ++                $decrypted ? 'Success' : 'Not encrypted'
    ++        );
    ++    }
    ++
    ++    return 1;
    ++}
    ++
    ++sub HandleErrors {
    ++    my %args = (
    ++        Message => undef,
    ++        Result => [],
    ++        @_
    ++    );
    ++
    ++    my $reject = 0;
    ++
    ++    my %sent_once = ();
    ++    foreach my $run ( @{ $args{'Result'} } ) {
    ++        my @status = RT::Crypt::GnuPG->ParseStatus( $run->{'status'} );
    ++        unless ( $sent_once{'NoPrivateKey'} ) {
    ++            unless ( CheckNoPrivateKey( Message => $args{'Message'}, Status => \@status ) ) {
    ++                $sent_once{'NoPrivateKey'}++;
    ++                $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnMissingPrivateKey'};
    ++            }
    ++        }
    ++        unless ( $sent_once{'BadData'} ) {
    ++            unless ( CheckBadData( Message => $args{'Message'}, Status => \@status ) ) {
    ++                $sent_once{'BadData'}++;
    ++                $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnBadData'};
    ++            }
    ++        }
    ++    }
    ++    return $reject;
    ++}
    ++
    ++sub CheckNoPrivateKey {
    ++    my %args = (Message => undef, Status => [], @_ );
    ++    my @status = @{ $args{'Status'} };
    ++
    ++    my @decrypts = grep $_->{'Operation'} eq 'Decrypt', @status;
    ++    return 1 unless @decrypts;
    ++    foreach my $action ( @decrypts ) {
    ++        # if at least one secrete key exist then it's another error
    ++        return 1 if
    ++            grep !$_->{'User'}{'SecretKeyMissing'},
    ++                @{ $action->{'EncryptedTo'} };
    ++    }
    ++
    ++    $RT::Logger->error("Couldn't decrypt a message: have no private key");
    ++
    ++    my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
    ++    my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
    ++        To        => $address,
    ++        Template  => 'Error: no private key',
    ++        Arguments => {
    ++            Message   => $args{'Message'},
    ++            TicketObj => $args{'Ticket'},
    ++        },
    ++        InReplyTo => $args{'Message'},
    ++    );
    ++    unless ( $status ) {
    ++        $RT::Logger->error("Couldn't send 'Error: no private key'");
    ++    }
    ++    return 0;
    ++}
    ++
    ++sub CheckBadData {
    ++    my %args = (Message => undef, Status => [], @_ );
    ++    my @bad_data_messages = 
    ++        map $_->{'Message'},
    ++        grep $_->{'Status'} ne 'DONE' && $_->{'Operation'} eq 'Data',
    ++        @{ $args{'Status'} };
    ++    return 1 unless @bad_data_messages;
    ++
    ++    $RT::Logger->error("Couldn't process a message: ". join ', ', @bad_data_messages );
    ++
    ++    my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
    ++    my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
    ++        To        => $address,
    ++        Template  => 'Error: bad GnuPG data',
    ++        Arguments => {
    ++            Messages  => [ @bad_data_messages ],
    ++            TicketObj => $args{'Ticket'},
    ++        },
    ++        InReplyTo => $args{'Message'},
    ++    );
    ++    unless ( $status ) {
    ++        $RT::Logger->error("Couldn't send 'Error: bad GnuPG data'");
    ++    }
    ++    return 0;
    ++}
    ++
    ++sub VerifyDecrypt {
    ++    my %args = (
    ++        Entity => undef,
    ++        @_
    ++    );
    ++
    ++    my @res = RT::Crypt->VerifyDecrypt( %args );
    ++    unless ( @res ) {
    ++        $RT::Logger->debug("No more encrypted/signed parts");
    ++        return 1;
    ++    }
    ++
    ++    $RT::Logger->debug('Found GnuPG protected parts');
    ++
    ++    # return on any error
    ++    if ( grep $_->{'exit_code'}, @res ) {
    ++        $RT::Logger->debug("Error during verify/decrypt operation");
    ++        return (0, @res);
    ++    }
    ++
    ++    # nesting
    ++    my ($status, @nested) = VerifyDecrypt( %args );
    ++    return $status, @res, @nested;
    ++}
    ++
    ++RT::Base->_ImportOverlays();
    ++
    ++1;
     
     diff --git a/lib/RT/Interface/Email/Auth/GnuPG.pm b/lib/RT/Interface/Email/Auth/GnuPG.pm
     --- a/lib/RT/Interface/Email/Auth/GnuPG.pm
    @@ -54,11 +298,11 @@
      
      sub ApplyBeforeDecode { return 1 }
      
    --use RT::Crypt::GnuPG;
    +-use RT::Crypt;
     -use RT::EmailParser ();
     +use RT::Interface::Email::Auth::Crypt;
      
    --sub GetCurrentUser {
    + sub GetCurrentUser {
     -    my %args = (
     -        Message       => undef,
     -        RawMessageRef => undef,
    @@ -76,7 +320,7 @@
     -    my $msg = $args{'Message'}->dup;
     -
     -    my ($status, @res) = VerifyDecrypt(
    --        Entity => $args{'Message'}, AddStatus => 1,
    +-        Entity => $args{'Message'},
     -    );
     -    if ( $status && !@res ) {
     -        $args{'Message'}->head->replace(
    @@ -110,7 +354,7 @@
     -
     -        my $status = $part->head->get( 'X-RT-GnuPG-Status' );
     -        if ( $status ) {
    --            for ( RT::Crypt::GnuPG::ParseStatus( $status ) ) {
    +-            for ( RT::Crypt::GnuPG->ParseStatus( $status ) ) {
     -                if ( $_->{Operation} eq 'Decrypt' && $_->{Status} eq 'DONE' ) {
     -                    $decrypted = 1;
     -                }
    @@ -142,7 +386,7 @@
     -
     -    my %sent_once = ();
     -    foreach my $run ( @{ $args{'Result'} } ) {
    --        my @status = RT::Crypt::GnuPG::ParseStatus( $run->{'status'} );
    +-        my @status = RT::Crypt::GnuPG->ParseStatus( $run->{'status'} );
     -        unless ( $sent_once{'NoPrivateKey'} ) {
     -            unless ( CheckNoPrivateKey( Message => $args{'Message'}, Status => \@status ) ) {
     -                $sent_once{'NoPrivateKey'}++;
    @@ -222,7 +466,7 @@
     -        @_
     -    );
     -
    --    my @res = RT::Crypt::GnuPG::VerifyDecrypt( %args );
    +-    my @res = RT::Crypt->VerifyDecrypt( %args );
     -    unless ( @res ) {
     -        $RT::Logger->debug("No more encrypted/signed parts");
     -        return 1;
    @@ -239,9 +483,23 @@
     -    # nesting
     -    my ($status, @nested) = VerifyDecrypt( %args );
     -    return $status, @res, @nested;
    --}
    -+sub GetCurrentUser { return RT::Interface::Email::Auth::Crypt::GetCurrentUser(@_) }
    ++    return RT::Interface::Email::Auth::Crypt::GetCurrentUser( @_ );
    + }
      
      RT::Base->_ImportOverlays();
    - 
     
    +diff --git a/share/html/Elements/ShowGnuPGStatus b/share/html/Elements/ShowGnuPGStatus
    +--- a/share/html/Elements/ShowGnuPGStatus
    ++++ b/share/html/Elements/ShowGnuPGStatus
    +@@
    +         return (0, "Couldn't parse content of attachment #". $original->id);
    +     }
    + 
    +-    use RT::Interface::Email::Auth::GnuPG;
    +-    my ($status, @res) = RT::Interface::Email::Auth::GnuPG::VerifyDecrypt( Entity => $entity );
    ++    use RT::Interface::Email::Auth::Crypt;
    ++    my ($status, @res) = RT::Interface::Email::Auth::Crypt::VerifyDecrypt( Entity => $entity );
    +     if ( $status && !@res ) {
    +         # imposible in this situation
    +         return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted");
    +
 19:  bfea306 < ---:  ------- abstracting and maintaining currently used protocol
 20:  2d27d0d < ---:  ------- ParseStatus in RT::Crypt
 21:  8aa3347 < ---:  ------- more on methods instead of functions
 22:  45556ff < ---:  ------- more conversions from funstions to class methods
 23:  8cc7aca < ---:  ------- when we SignEncrypt store protocol in results
 24:  1554a6a < ---:  ------- switch to using class methods, for example generic ParseStatus
 25:  3529aa4 < ---:  ------- convert t/mail/crypt-gnupg.t over new API
 26:  46ce34c < ---:  ------- partialy convert ShowGnuPGStatus over new API
 27:  6698157 < ---:  ------- more on ::ParseStatus ->  ->ParseStatus
 28:  5912857 < ---:  ------- syntax error fixes
 29:  1c46a51 < ---:  ------- RT::Crypt->LoadImplementation method
 31:  09467a4 < ---:  ------- pass parents into FindScatteredParts
 32:  431cca9 < ---:  ------- set Protocol when we find scattered part(s)
 34:  cdd23e3 < ---:  ------- without parents we can not complete detection of some parts
 35:  0f985c4 < ---:  ------- adjust tests, now we support additional detection
---:  ------- >  42:  602dc5a Warn about Auth::GnuPG and Auth::SMIME MailPlugins, and switch to Auth::Crypt
---:  ------- >  43:  f81211a Remove unnecessary GnuPG disabling during testing
---:  ------- >  44:  dad0e76 Generalize RT::Interface::Email::Auth::Crypt for multiple protocols
---:  ------- >  45:  64a9b43 By default, VerifyDecrypt should iterate to fixed-point
---:  ------- >  46:  1c68fcb Generalize GnuPG re-verification
---:  ------- >  47:  71a39b4 Remove an unused variable
---:  ------- >  48:  45002d0 Abstract out a general Crypt setting, and split incoming and outgoing
---:  ------- >  49:  c6d664d Handle if the incoming protocol is but a scalar
---:  ------- >  50:  32147d9 Move GnuPG enabling/disabling to GnuPG PostLoadCheck
---:  ------- >  51:  b4b6f18 Move canonicalization of GnuPG homedir to PostLoadCheck
---:  ------- >  52:  9aed02a Ensure that RT->Config->Options returns keys in consistent order
---:  ------- >  53:  d5c6da1 Add "Probe" as a requirement of RT::Crypt::Role
---:  ------- >  54:  e03e44b Allow safe_run_child to run before ConnectToDatabase runs
---:  ------- >  55:  8b9d934 Genericize loading and ->Probe of RT::Crypt::* classes during PostLoadCheck
154:  748e2cf !  56:  9cf1908 Don't load crypt implementations upon RT::Crypt load
    @@ -1,19 +1,19 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    don't load crypt implementations right away
    +    Don't load crypt implementations upon RT::Crypt load
         
    -    loading GnuPG module fails if you don't have required
    -    modules
    +    Loading RT::Crypt::GnuPG fails if the optional depedencies are not met,
    +    for example.  As RT::Crypt is always loaded, delay loading of the
    +    implementation class until we know that we are enabling it.
     
     diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
     --- a/lib/RT/Crypt.pm
     +++ b/lib/RT/Crypt.pm
     @@
    - 
      package RT::Crypt;
    + use 5.010;
      
     -require RT::Crypt::GnuPG;
    --require RT::Crypt::SMIME;
     -
      =head1 NAME
      
---:  ------- >  57:  12bd3b0 Drop extraneous "require RT::Crypt" lines
---:  ------- >  58:  66826e7 Switch iterations over all protocols to merely enabled ones
---:  ------- >  59:  90d1eef Place Passphrase configuration on individual configurations
---:  ------- >  60:  a7b88b0 Move RejectOnMissingPrivateKey and RejectOnBadData to generic Crypt settings
135:  35a7130 !  61:  c0e3f4b Ensure that ContentType is only updated after successful encryption/decryption
    @@ -1,10 +1,19 @@
    -Author: Ruslan Zakirov <ruz at bestpractical.com>
    +Author: Alex Vandiver <alexmv at bestpractical.com>
     
    -    delay setting content type until all error checks are passed
    +    Ensure that ContentType is only updated after successful encryption/decryption
     
     diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
     --- a/lib/RT/Attachment.pm
     +++ b/lib/RT/Attachment.pm
    +@@
    +         return (1, $self->loc('Already encrypted'));
    +     } elsif ( $type =~ /^multipart\//i ) {
    +         return (1, $self->loc('No need to encrypt'));
    +-    } else {
    +-        $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
    +     }
    + 
    +     my $queue = $txn->TicketObj->QueueObj;
     @@
              return (0, $self->loc('No key suitable for encryption'));
          }
    @@ -13,12 +22,14 @@
     -    $self->SetHeader( 'Content-Type' => $type );
     -
          my $content = $self->Content;
    -     my %res = RT::Crypt->SignEncryptContent(
    +     my %res = RT::Crypt::GnuPG->SignEncryptContent(
              Content => \$content,
     @@
          unless ( $status ) {
              return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg));
          }
    ++
    ++    $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
     +    $self->__Set( Field => 'ContentType', Value => $type );
     +    $self->SetHeader( 'Content-Type' => $type );
     +
    @@ -33,7 +44,7 @@
     -    $self->SetHeader( 'Content-Type' => $type );
      
          my $content = $self->Content;
    -     my %res = RT::Crypt->DecryptContent( Content => \$content );
    +     my %res = RT::Crypt::GnuPG->DecryptContent( Content => \$content, );
     @@
          unless ( $status ) {
              return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg));
---:  ------- >  62:  086b633 Refactor encryption of attachment content into the role, and move config
---:  ------- >  63:  c7cfc5a Switch to generic Crypt checks instead of GnuPG
---:  ------- >  64:  79e541e All outgoing defaults should default to UseForOutgoing, not GnuPG
---:  ------- >  65:  f44e951 Fix a typo in a comment
 52:  24cbe5e !  66:  5dd001a Don't report unsafe permissions on gpg tests
    @@ -1,6 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    don't report unsafe permissions on gpg tests
    +    Don't report unsafe permissions on gpg tests
     
     diff --git a/t/mail/gnupg-incoming.t b/t/mail/gnupg-incoming.t
     --- a/t/mail/gnupg-incoming.t
 86:  20f6b23 !  67:  bc0e44b One entity may have information about multiple crypt runs
    @@ -1,6 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    one entity may have information about multiple crypt runs
    +    One entity may have information about multiple crypt runs
     
     diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
     --- a/lib/RT/Interface/Email/Auth/Crypt.pm
---:  ------- >  68:  34ac88b Rename "bad data" template to not be GnuPG-specific
---:  ------- >  69:  7f3785e Move non-GPG specific docs to RT::Crypt
---:  ------- >  70:  82e3e61 Wording fixes for RT::Crypt::GnuPG documentation
 36:  dc71bc5 !  71:  e854b0c Add a 'configure' option to enable SMIME support
    @@ -1,6 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    add option --enable-smime
    +    Add a 'configure' option to enable SMIME support
     
     diff --git a/configure.ac b/configure.ac
     --- a/configure.ac
    @@ -11,7 +11,7 @@
      
     +dnl RT's SMIME support
     +AC_CHECK_PROG([RT_SMIME], [openssl], "yes", "no")
    -+AC_ARG_ENABLE(gpg,
    ++AC_ARG_ENABLE(smime,
     +            AC_HELP_STRING([--enable-smime],
     +                           [Turns on Secure MIME (SMIME) support]),
     +            RT_SMIME=$enableval)
 37:  dec2fd8 < ---:  ------- bring new config options required for SMIM and genric interface
 38:  bb2f597 < ---:  ------- check old and new crypt related config options
 39:  da3496f < ---:  ------- base class for cryptography protocols
 40:  c184eb2 < ---:  ------- change cryptography config
 41:  6b09e47 < ---:  ------- minor GnuPG changes
 42:  39718d5 < ---:  ------- config based methods in RT::Crypt
 43:  eca79bd < ---:  ------- return asap if there is no parts for scattered search
 44:  d5d8bdd < ---:  ------- generic crypt interface for keys information retrieval
 45:  59bbcfe < ---:  ------- clean GnuPG's keys info implementation
 46:  3ed0f64 < ---:  ------- in Admin interface show keys info for every enabled protocol
 47:  94254af < ---:  ------- variouse fixes in web ui regarding crypto changes
 48:  8b649de < ---:  ------- update tests
 49:  a4a38f8 < ---:  ------- refactor UseKeyFor* and GetKeysFor* for generic use
 50:  a2b4c59 < ---:  ------- missing semicolon
 51:  4bb971a < ---:  ------- fix parts_DFS like filter we have, we need only leafs
 53:  b550a4e < ---:  ------- s/Incomming/Incoming/
---:  ------- >  72:  782b13f Add the skeleton of SMIME support, in RT::Crypt::SMIME
---:  ------- >  73:  f751318 SMIME: Store the path to openssl in a configuration option
---:  ------- >  74:  7825d71 SMIME: probe for openssl existance, and smime subcommand
---:  ------- >  75:  5ccb49b SMIME: part detection
---:  ------- >  76:  80e63c5 SMIME: Format status into headers
---:  ------- >  77:  f1b1536 SMIME: Read and parse key content from a Keyring directory
 54:  02e8403 !  78:  a5383fa SMIME: Ensure that the keyring path is absolute, and exists
    @@ -1,6 +1,14 @@
    -Author: Ruslan Zakirov <ruz at bestpractical.com>
    +Author: Alex Vandiver <alexmv at bestpractical.com>
     
    -    PostLoadCheck for SMIME config option
    +    SMIME: Ensure that the keyring path is absolute, and exists
    +    
    +    If it does not exist, verification and outgoing encryption (anything
    +    that does not require a private key) is possible; as such, warn but do
    +    not disable.
    +    
    +    Because PostLoadChecks for SMIME and GnuPG may run more than once (as
    +    called by the Crypt PostLoadCheck), also ensure that Keyring warning
    +    does not occur more than once.
     
     diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
     --- a/lib/RT/Config.pm
    @@ -17,27 +25,21 @@
     +            my $opt = $self->Get('SMIME');
     +            return unless $opt->{'Enable'};
     +
    -+            my $path = $opt->{'OpenSSL'};
    -+            if ( defined $path ) {
    -+                unless ( -e $path && -x _) {
    -+                    $RT::Logger->debug(
    -+                        "'$path' doesn't exist or is not an executable."
    -+                        ." Please change SMIME->OpenSSL option."
    -+                        ." SMIME support has been disabled"
    -+                    );
    -+                    $opt->{'Enable'} = 0;
    -+                    return;
    ++            if (exists $opt->{Keyring}) {
    ++                unless ( File::Spec->file_name_is_absolute( $opt->{Keyring} ) ) {
    ++                    $opt->{Keyring} = File::Spec->catfile( $RT::BasePath, $opt->{Keyring} );
     +                }
    -+            } else {
    -+                $opt->{'OpenSSL'} = 'openssl';
    -+            }
    -+
    -+            unless ( ref $opt->{'Passphrase'} eq 'HASH' ) {
    -+                $opt->{'Passphrase'} = { '' => $opt->{'Passphrase'} };
    ++                unless (-d $opt->{Keyring} and -r _) {
    ++                    $RT::Logger->info(
    ++                        "RT's SMIME libraries couldn't successfully read your".
    ++                        " configured SMIME keyring directory (".$opt->{Keyring}
    ++                        .").");
    ++                    delete $opt->{Keyring};
    ++                }
     +            }
     +        },
     +    },
    -     GnuPG        => { Type => 'HASH' },
    -     GnuPGOptions => {
    +     GnuPG        => {
              Type => 'HASH',
    +         PostLoadCheck => sub {
     
 55:  d181350 < ---:  ------- new functions for testing: smime, temp storang, relocatable
 56:  2823fc7 < ---:  ------- expect that we get Queue and Actions arguments and pass them further
---:  ------- >  79:  2db873e SMIME: Store user keys in a user column
---:  ------- >  80:  d12d753 SMIME: Message verification
---:  ------- >  81:  f65e4ac SMIME: Import signing keys after verification
---:  ------- >  82:  02f5845 SMIME: Verifying the signing entity of SMIME certificates
---:  ------- >  83:  93df8fc SMIME: Allow an insecure mode which accepts untrusted certificates
---:  ------- >  84:  03df19e SMIME: Document passphrase loading
 57:  19a0900 !  85:  fe3e256 Pass queue and actions into mail plugins, and thence to VerifyDecrypt
    @@ -1,6 +1,9 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    pass "queue" and actions into early mail plugins
    +    Pass queue and actions into mail plugins, and thence to VerifyDecrypt
    +    
    +    This allows crypt plugins to examine the queue's configured addresses,
    +    and use that information when determining which keys to decrypt with.
     
     diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
     --- a/lib/RT/Interface/Email.pm
    @@ -46,3 +49,24 @@
          unless ( $SystemTicket->id || $SystemQueueObj->id ) {
              return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
     
    +diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
    +--- a/lib/RT/Interface/Email/Auth/Crypt.pm
    ++++ b/lib/RT/Interface/Email/Auth/Crypt.pm
    +@@
    +     my %args = (
    +         Message       => undef,
    +         RawMessageRef => undef,
    ++        Queue         => undef,
    ++        Actions       => undef,
    +         @_
    +     );
    + 
    +@@
    +     }
    + 
    +     my (@res) = RT::Crypt->VerifyDecrypt(
    ++        %args,
    +         Entity => $args{'Message'},
    +     );
    +     if ( !@res ) {
    +
 58:  37967e7 < ---:  ------- s/Incomming/Incoming/
 59:  1fd72af < ---:  ------- add smime data for testing
 60:  5e0cb8c < ---:  ------- tests for SMIME
 61:  5a2479e < ---:  ------- SMIME crypt implementation
 62:  c742957 < ---:  ------- fix getting keys per protocol
 63:  09f9358 < ---:  ------- debug log in tmp dir may help debug things
 64:  6f94710 < ---:  ------- SignEncrypt should use enabled outgoing protocol
 66:  5a83927 < ---:  ------- represent basic GetKeysForEncryption in RT::Crypt::Base
 68:  3b23cf1 < ---:  ------- per UID expiration dates make sense only for GnuPG
 69:  bcb936d < ---:  ------- mass Crypt::SMIME improvement
 70:  38241fb < ---:  ------- check proper config option
 71:  7e95b4a < ---:  ------- switch to new APIs
 72:  54b82f2 < ---:  ------- update tests
 73:  df669a4 < ---:  ------- rename test files
 75:  2db72ac < ---:  ------- rename key files for smime
 76:  e0b42db < ---:  ------- tests changes
 77:  7845b3b < ---:  ------- create our CA and generate keys from scratch
---:  ------- >  86:  c56f040 SMIME: Message decryption
---:  ------- >  87:  7acc2d8 SMIME: Testing keys and certificates
---:  ------- >  88:  3c3f958 Factor out find_relocatable_path
---:  ------- >  89:  c102a7f SMIME: Add a testing module
---:  ------- >  90:  b46b7d3 SMIME: Test incoming mail verification and encryption
---:  ------- >  91:  37f1d2a SMIME: Message signing and encryption
107:  7cd1ced !  92:  f289b2f SMIME: If passphrase is empty, then don't provide -passin
    @@ -1,6 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    if passphrase is empty then we shouldn't use -passin
    +    SMIME: If passphrase is empty, then don't provide -passin
     
     diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
     --- a/lib/RT/Crypt/SMIME.pm
    @@ -11,8 +11,8 @@
              push @command, join ' ', shell_quote(
     -            $self->OpenSSLPath, qw(smime -sign -passin env:SMIME_PASS),
     +            $self->OpenSSLPath, qw(smime -sign),
    -             -signer => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
    -             -inkey  => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
    +             -signer => $file,
    +             -inkey  => $file,
     +            (defined $args{'Passphrase'} && length $args{'Passphrase'})
     +                ? (qw(-passin env:SMIME_PASS))
     +                : (),
    @@ -21,15 +21,15 @@
          if ( $args{'Encrypt'} ) {
     @@
              local $SIG{CHLD} = 'DEFAULT';
    -         my $cmd = join( ' ', shell_quote(
    +         my $cmd = [
                  $self->OpenSSLPath,
     -            qw(smime -decrypt -passin env:SMIME_PASS),
    --            -recip => $key_file,
    -+            qw(smime -decrypt), '-recip' => $key_file,
    ++            qw(smime -decrypt),
    +             -recip => $file,
     +            (defined $ENV{'SMIME_PASS'} && length $ENV{'SMIME_PASS'})
     +                ? (qw(-passin env:SMIME_PASS))
     +                : (),
    -         ) );
    +         ];
              safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
    -         unless ( $? ) {
    +         $res{'exit_code'} = $?;
     
 79:  187da04 !  93:  49fc5c4 SMIME: Test outgoing mail
    @@ -1,269 +1,182 @@
    -Author: Ruslan Zakirov <ruz at bestpractical.com>
    +Author: Alex Vandiver <alexmv at bestpractical.com>
     
    -    add and update SMIME tests
    +    SMIME: Test outgoing mail
     
    -diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
    ---- a/t/mail/smime/incoming.t
    -+++ b/t/mail/smime/incoming.t
    +diff --git a/t/crypt/smime/bad-recipients.t b/t/crypt/smime/bad-recipients.t
    +new file mode 100644
    +--- /dev/null
    ++++ b/t/crypt/smime/bad-recipients.t
     @@
    - );
    - 
    - RT->Config->Set( Crypt =>
    -+    Enable   => 1,
    -     Incoming => ['SMIME'],
    -     Outgoing => 'SMIME',
    - );
    ++use strict;
    ++use warnings;
    ++
    ++use RT::Test::SMIME tests => undef;
    ++
    ++use RT::Tickets;
    ++
    ++RT::Test::SMIME->import_key('sender at example.com');
    ++my $queue = RT::Test->load_or_create_queue(
    ++    Name              => 'General',
    ++    CorrespondAddress => 'sender at example.com',
    ++);
    ++ok $queue && $queue->id, 'loaded or created queue';
    ++
    ++{
    ++    my ($status, $msg) = $queue->SetEncrypt(1);
    ++    ok $status, "turn on encyption by default"
    ++        or diag "error: $msg";
    ++}
    ++
    ++my $root;
    ++{
    ++    $root = RT::User->new($RT::SystemUser);
    ++    ok($root->LoadByEmail('root at localhost'), "Loaded user 'root'");
    ++    ok($root->Load('root'), "Loaded user 'root'");
    ++    is($root->EmailAddress, 'root at localhost');
    ++
    ++    RT::Test::SMIME->import_key( 'root at example.com.crt' => $root );
    ++}
    ++
    ++my $bad_user;
    ++{
    ++    $bad_user = RT::Test->load_or_create_user(
    ++        Name => 'bad_user',
    ++        EmailAddress => 'baduser at example.com',
    ++    );
    ++    ok $bad_user && $bad_user->id, 'created a user without key';
    ++}
    ++
    ++RT::Test->clean_caught_mails;
    ++
    ++use Test::Warn;
    ++
    ++warnings_like {
    ++    my $ticket = RT::Ticket->new(RT->SystemUser);
    ++    my ($status, undef, $msg) = $ticket->Create( Queue => $queue->id, Requestor => [$root->id, $bad_user->id] );
    ++    ok $status, "created a ticket" or diag "error: $msg";
    ++
    ++    my @mails = RT::Test->fetch_caught_mails;
    ++    is scalar @mails, 3, "autoreply, to bad user, to RT owner";
    ++
    ++    like $mails[0], qr{To: baduser\@example\.com}, "notification to bad user";
    ++    like $mails[1], qr{To: root}, "notification to RT owner";
    ++    like $mails[1], qr{Recipient 'baduser\@example\.com' is unusable, the reason is 'Key not found'},
    ++        "notification to owner has error";
    ++} [qr{Recipient 'baduser\@example\.com' is unusable, the reason is 'Key not found'}];
    ++
    ++done_testing;
     
     diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
    ---- a/t/mail/smime/outgoing.t
    +new file mode 100644
    +--- /dev/null
     +++ b/t/mail/smime/outgoing.t
     @@
    -     ok(eval {
    -         run3([
    -             $openssl, qw(smime -decrypt -passin pass:123456),
    --            '-inkey', File::Spec->catfile($keys, 'recipient.key'),
    --            '-recip', File::Spec->catfile($keys, 'recipient.crt')
    -+            '-inkey', File::Spec->catfile($keys, 'root at example.com.key'),
    -+            '-recip', File::Spec->catfile($keys, 'root at example.com.crt')
    -         ], \$mails[0], \$buf, \$err )
    -         }, 'can decrypt'
    -     );
    -
    -diff --git a/t/mail/smime/realmail.t b/t/mail/smime/realmail.t
    -new file mode 100644
    ---- /dev/null
    -+++ b/t/mail/smime/realmail.t
    -@@
    -+#!/usr/bin/perl
     +use strict;
     +use warnings;
     +
    -+use RT::Test tests => 197;
    -+
    -+my $openssl = RT::Test->find_executable('openssl');
    -+plan skip_all => 'openssl executable is required.'
    -+    unless $openssl;
    -+
    -+use Digest::MD5 qw(md5_hex);
    -+
    -+my $mails = RT::Test::get_abs_relocatable_dir(
    -+    (File::Spec->updir()) x 2,
    -+    qw(data smime mails),
    -+);
    -+my $keyring = RT::Test->new_temp_dir(
    -+    crypt => smime => 'smime_keyring'
    -+);
    -+
    -+RT->Config->Set( Crypt =>
    -+    Enable   => 1,
    -+    Incoming => ['SMIME'],
    -+    Outgoing => 'SMIME',
    -+);
    -+RT->Config->Set( GnuPG => Enable => 0 );
    -+RT->Config->Set( SMIME =>
    -+    Enable => 1,
    -+    Passphrase => {
    -+        'root at example.com' => '123456',
    -+    },
    -+    OpenSSL => $openssl,
    -+    Keyring => $keyring,
    -+);
    -+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
    -+
    -+RT::Test->import_smime_key('root at example.com');
    -+RT::Test->import_smime_key('sender at example.com');
    -+
    -+my ($baseurl, $m) = RT::Test->started_ok;
    -+ok $m->login, 'we did log in';
    -+$m->get_ok( '/Admin/Queues/');
    -+$m->follow_link_ok( {text => 'General'} );
    -+$m->submit_form( form_number => 3,
    -+         fields      => { CorrespondAddress => 'root at example.com' } );
    -+
    -+diag "load Everyone group" if $ENV{'TEST_VERBOSE'};
    -+my $everyone;
    -+{
    -+    $everyone = RT::Group->new( $RT::SystemUser );
    -+    $everyone->LoadSystemInternalGroup('Everyone');
    -+    ok $everyone->id, "loaded 'everyone' group";
    -+}
    -+
    -+RT::Test->set_rights(
    -+    Principal => $everyone,
    -+    Right => ['CreateTicket'],
    -+);
    -+
    -+
    -+my $eid = 0;
    -+for my $usage (qw/signed encrypted signed&encrypted/) {
    -+    for my $attachment (qw/plain text-attachment binary-attachment/) {
    -+        ++$eid;
    -+        diag "Email $eid: $usage, $attachment email" if $ENV{TEST_VERBOSE};
    -+        eval { email_ok($eid, $usage, $attachment) };
    -+    }
    -+}
    -+
    -+sub email_ok {
    -+    my ($eid, $usage, $attachment) = @_;
    -+    diag "email_ok $eid: $usage, $attachment" if $ENV{'TEST_VERBOSE'};
    -+
    -+    my ($file) = glob("$mails/$eid-*");
    -+    my $mail = RT::Test->file_content($file);
    -+
    -+    my ($status, $id) = RT::Test->send_via_mailgate($mail);
    -+    is ($status >> 8, 0, "$eid: The mail gateway exited normally");
    -+    ok ($id, "$eid: got id of a newly created ticket - $id");
    -+
    -+    my $tick = RT::Ticket->new( $RT::SystemUser );
    -+    $tick->Load( $id );
    -+    ok ($tick->id, "$eid: loaded ticket #$id");
    -+
    -+    is ($tick->Subject,
    -+        "Test Email ID:$eid",
    -+        "$eid: Created the ticket"
    ++use RT::Test::SMIME tests => undef;
    ++my $test = 'RT::Test::SMIME';
    ++
    ++use IPC::Run3 'run3';
    ++use RT::Interface::Email;
    ++
    ++my ($url, $m) = RT::Test->started_ok;
    ++ok $m->login, "logged in";
    ++
    ++my $queue = RT::Test->load_or_create_queue(
    ++    Name              => 'General',
    ++    CorrespondAddress => 'sender at example.com',
    ++    CommentAddress    => 'sender at example.com',
    ++);
    ++ok $queue && $queue->id, 'loaded or created queue';
    ++
    ++{
    ++    my ($status, $msg) = $queue->SetEncrypt(1);
    ++    ok $status, "turn on encyption by default"
    ++        or diag "error: $msg";
    ++}
    ++
    ++my $user;
    ++{
    ++    $user = RT::User->new($RT::SystemUser);
    ++    ok($user->LoadByEmail('root at localhost'), "Loaded user 'root'");
    ++    ok($user->Load('root'), "Loaded user 'root'");
    ++    is($user->EmailAddress, 'root at localhost');
    ++
    ++    RT::Test::SMIME->import_key( 'root at example.com.crt' => $user );
    ++}
    ++
    ++RT::Test->clean_caught_mails;
    ++
    ++{
    ++    my $mail = <<END;
    ++From: root\@localhost
    ++To: rt\@example.com
    ++Subject: This is a test of new ticket creation as an unknown user
    ++
    ++Blah!
    ++Foob!
    ++
    ++END
    ++
    ++    my ($status, $id) = RT::Test->send_via_mailgate(
    ++        $mail, queue => $queue->Name,
     +    );
    -+
    -+    my $txn = $tick->Transactions->First;
    -+    my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
    -+
    -+    is( $msg->GetHeader('X-RT-Privacy'),
    -+        'SMIME',
    -+        "$eid: recorded incoming mail that is secured"
    ++    is $status >> 8, 0, "successfuly executed mailgate";
    ++
    ++    my $ticket = RT::Ticket->new($RT::SystemUser);
    ++    $ticket->Load( $id );
    ++    ok ($ticket->id, "found ticket ". $ticket->id);
    ++}
    ++
    ++{
    ++    my @mails = RT::Test->fetch_caught_mails;
    ++    is scalar @mails, 1, "autoreply";
    ++
    ++    my ($buf, $err);
    ++    local $@;
    ++    ok(eval {
    ++        run3([
    ++            qw(openssl smime -decrypt -passin pass:123456),
    ++            '-inkey', $test->key_path('root at example.com.key'),
    ++            '-recip', $test->key_path('root at example.com.crt')
    ++        ], \$mails[0], \$buf, \$err )
    ++        }, 'can decrypt'
     +    );
    -+
    -+    if ($usage =~ /encrypted/) {
    -+        is( $msg->GetHeader('X-RT-Incoming-Encryption'),
    -+            'Success',
    -+            "$eid: recorded incoming mail that is encrypted"
    -+        );
    -+        like( $attachments[0]->Content, qr/ID:$eid/,
    -+                "$eid: incoming mail did NOT have original body"
    -+        );
    -+    }
    -+    else {
    -+        is( $msg->GetHeader('X-RT-Incoming-Encryption'),
    -+            'Not encrypted',
    -+            "$eid: recorded incoming mail that is not encrypted"
    -+        );
    -+        like( $msg->Content || $attachments[0]->Content, qr/ID:$eid/,
    -+            "$eid: got original content"
    -+        );
    -+    }
    -+
    -+    if ($usage =~ /signed/) {
    -+        is( $msg->GetHeader('X-RT-Incoming-Signature'),
    -+            'RT Test <sender at example.com>',
    -+            "$eid: recorded incoming mail that is signed"
    -+        );
    -+    }
    -+    else {
    -+        is( $msg->GetHeader('X-RT-Incoming-Signature'),
    -+            undef,
    -+            "$eid: recorded incoming mail that is not signed"
    -+        );
    -+    }
    -+
    -+    if ($attachment =~ /attachment/) {
    -+        my ($a) = grep $_->Filename, @attachments;
    -+        ok ($a && $a->Id, "$eid: found attachment with filename");
    -+
    -+        my $acontent = $a->Content;
    -+        if ($attachment =~ /binary/)
    -+        {
    -+            is(md5_hex($acontent), '1e35f1aa90c98ca2bab85c26ae3e1ba7', "$eid: The binary attachment's md5sum matches");
    -+        }
    -+        else
    -+        {
    -+            like($acontent, qr/zanzibar/, "$eid: The attachment isn't screwed up in the database.");
    -+        }
    -+    }
    -+
    -+    return 0;
    -+}
    -+
    ++    diag $@ if $@;
    ++    diag $err if $err;
    ++    diag "Error code: $?" if $?;
    ++    like($buf, qr'This message has been automatically generated in response');
    ++}
    ++
    ++undef $m;
    ++done_testing;
     
     diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
     new file mode 100644
     --- /dev/null
     +++ b/t/web/smime/outgoing.t
     @@
    -+#!/usr/bin/perl -w
     +use strict;
     +use warnings;
     +
    -+use RT::Test tests => 492;
    -+
    -+my $openssl = RT::Test->find_executable('openssl');
    -+plan skip_all => 'openssl executable is required.'
    -+    unless $openssl;
    ++use RT::Test::SMIME tests => undef;
    ++my $test = 'RT::Test::SMIME';
     +
     +use RT::Action::SendEmail;
     +use File::Temp qw(tempdir);
     +
    -+RT::Test->set_mail_catcher;
    -+
     +use_ok('RT::Crypt::SMIME');
     +
    -+my $keys = RT::Test::get_abs_relocatable_dir(
    -+    (File::Spec->updir()) x 2,
    -+    qw(data smime keys),
    -+);
    -+
    -+my $keyring = RT::Test->new_temp_dir(
    -+    crypt => smime => 'smime_keyring'
    -+);
    -+
    -+RT->Config->Set( Crypt =>
    -+    Enable   => 1,
    -+    Incoming => ['SMIME'],
    -+    Outgoing => 'SMIME',
    -+);
    -+RT->Config->Set( GnuPG => Enable => 0 );
    -+RT->Config->Set( SMIME =>
    -+    Enable => 1,
    -+    OutgoingMessagesFormat => 'RFC',
    -+    Passphrase => {
    -+        'sender at example.com' => '123456',
    -+    },
    -+    OpenSSL => $openssl,
    -+    Keyring => $keyring,
    -+);
    -+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
    -+
    -+RT::Test->import_smime_key('sender at example.com');
    -+
    -+{
    -+    my $cf = RT::CustomField->new( $RT::SystemUser );
    -+    my ($ret, $msg) = $cf->Create(
    -+        Name       => 'SMIME Key',
    -+        LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
    -+        Type       => 'TextSingle',
    ++RT::Test::SMIME->import_key('sender at example.com');
    ++
    ++my $user_email = 'root at example.com';
    ++{
    ++    my $user = RT::Test->load_or_create_user(
    ++        Name => $user_email, EmailAddress => $user_email
     +    );
    -+    ok($ret, "Custom Field created");
    -+
    -+    my $OCF = RT::ObjectCustomField->new( $RT::SystemUser );
    -+    $OCF->Create(
    -+        CustomField => $cf->id,
    -+        ObjectId    => 0,
    -+    );
    -+}
    -+
    -+my $user_email = 'root at example.com';
    -+{
    -+    my $user = RT::User->new( $RT::SystemUser );
    -+    $user->LoadByEmail( $user_email );
    -+    unless ( $user->id ) {
    -+        $user->Create(
    -+            Name         => $user_email,
    -+            EmailAddress => $user_email,
    -+            Privileged   => 1,
    -+        );
    -+    }
    -+    my ($status, $msg) = $user->AddCustomFieldValue(
    -+        Field => 'SMIME Key',
    -+        Value => RT::Test->file_content([$keys, $user_email .'.crt']),
    -+    );
    -+    ok $status, "set key for the user" or diag "error: $msg";
    ++    ok $user && $user->id, 'loaded or created user';
    ++    RT::Test::SMIME->import_key($user_email, $user);
     +}
     +
     +my $queue = RT::Test->load_or_create_queue(
    @@ -373,13 +286,14 @@
     +
     +# ------------------------------------------------------------------------------
     +# now delete all keys from the keyring and put back secret/pub pair for rt-test@
    -+# and only public key for rt-recipient@ so we can verify signatures and decrypt
    ++# and only public key for sender@ so we can verify signatures and decrypt
     +# like we are on another side recieving emails
     +# ------------------------------------------------------------------------------
     +
    ++my $keyring = $test->keyring_path;
     +unlink $_ foreach glob( $keyring ."/*" );
    -+RT::Test->import_smime_key('sender at example.com', 'public');
    -+RT::Test->import_smime_key($user_email);
    ++RT::Test::SMIME->import_key('sender at example.com.crt');
    ++RT::Test::SMIME->import_key($user_email);
     +
     +$queue = RT::Test->load_or_create_queue(
     +    Name              => 'Regression',
    @@ -406,7 +320,7 @@
     +    ok !$msg->GetHeader('X-RT-Incoming-Signature'),
     +        "RT's outgoing mail looks not signed";
     +
    -+    like $msg->Content, qr/Some content/, "RT's mail includes copy of ticket text";
    ++    like $txn->Content, qr/Some content/, "RT's mail includes copy of ticket text";
     +}
     +
     +foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed'} } ) {
    @@ -426,7 +340,7 @@
     +    is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted',
     +        "RT's outgoing mail looks not encrypted";
     +    like $msg->GetHeader('X-RT-Incoming-Signature'),
    -+        qr/<rt-recipient\@example.com>/,
    ++        qr/<sender\@example\.com>/,
     +        "RT's outgoing mail looks signed";
     +
     +    like $attachments[0]->Content, qr/Some content/,
    @@ -473,7 +387,7 @@
     +    is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success',
     +        "RT's outgoing mail looks encrypted";
     +    like $msg->GetHeader('X-RT-Incoming-Signature'),
    -+        qr/<rt-recipient\@example.com>/,
    ++        qr/<sender\@example.com>/,
     +        "RT's outgoing mail looks signed";
     +
     +    like $attachments[0]->Content, qr/Some content/,
    @@ -543,6 +457,9 @@
     +    check_text_emails( \%args, @mail );
     +}
     +
    ++undef $m;
    ++done_testing;
    ++
     +sub check_text_emails {
     +    my %args = %{ shift @_ };
     +    my @mail = @_;
    @@ -558,9 +475,9 @@
     +        }
     +
     +        if ( $args{'Encrypt'} ) {
    -+            like $mail, qr/application\/x-pkcs7-mime/, 'outgoing email was processed';
    ++            like $mail, qr/application\/(?:x-)?pkcs7-mime/, 'outgoing email was processed';
     +        } elsif ( $args{'Sign'} ) {
    -+            like $mail, qr/x-pkcs7-signature/, 'outgoing email was processed';
    ++            like $mail, qr/(?:x-)?pkcs7-signature/, 'outgoing email was processed';
     +        } else {
     +            unlike $mail, qr/smime/, 'outgoing email was not processed';
     +        }
    @@ -581,7 +498,7 @@
     +    # strip id from subject to create new ticket
     +    $mail =~ s/^(Subject:)\s*\[.*?\s+#\d+\]\s*/$1 /m;
     +    # strip several headers
    -+    foreach my $field ( qw(Message-ID X-RT-Original-Encoding RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
    ++    foreach my $field ( qw(Message-ID RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
     +        $mail =~ s/^$field:.*?\n(?! |\t)//gmsi;
     +    }
     +    return $mail;
 78:  070a573 !  94:  62a59a6 SMIME: Test parsing of real mail
    @@ -1,6 +1,6 @@
    -Author: Ruslan Zakirov <ruz at bestpractical.com>
    -
    -    add set of emails generated by thunderbird
    +Author: Alex Vandiver <alexmv at bestpractical.com>
    +
    +    SMIME: Test parsing of real mail
     
     diff --git a/t/data/smime/mails/1-signed.eml b/t/data/smime/mails/1-signed.eml
     new file mode 100644
    @@ -754,3 +754,134 @@
     +VJl16vAQxKLzOzQET0xzS5Jic/d6ponxjcBiXLsTxNnS/DHXkPpDWzz+2Fy9p7sz7NScH9+3
     +qwQIeNe1tczOd/sAAAAAAAAAAAAA
     
    +diff --git a/t/mail/smime/realmail.t b/t/mail/smime/realmail.t
    +new file mode 100644
    +--- /dev/null
    ++++ b/t/mail/smime/realmail.t
    +@@
    ++use strict;
    ++use warnings;
    ++
    ++use RT::Test::SMIME tests => undef;
    ++use Digest::MD5 qw(md5_hex);
    ++
    ++my $test = 'RT::Test::SMIME';
    ++my $mails = $test->mail_set_path;
    ++
    ++RT->Config->Get('SMIME')->{AcceptUntrustedCAs} = 1;
    ++
    ++RT::Test::SMIME->import_key('root at example.com');
    ++RT::Test::SMIME->import_key('sender at example.com');
    ++
    ++my ($baseurl, $m) = RT::Test->started_ok;
    ++ok $m->login, 'we did log in';
    ++$m->get_ok( '/Admin/Queues/');
    ++$m->follow_link_ok( {text => 'General'} );
    ++$m->submit_form( form_number => 3,
    ++         fields      => { CorrespondAddress => 'root at example.com' } );
    ++
    ++diag "load Everyone group" if $ENV{'TEST_VERBOSE'};
    ++my $everyone;
    ++{
    ++    $everyone = RT::Group->new( $RT::SystemUser );
    ++    $everyone->LoadSystemInternalGroup('Everyone');
    ++    ok $everyone->id, "loaded 'everyone' group";
    ++}
    ++
    ++RT::Test->set_rights(
    ++    Principal => $everyone,
    ++    Right => ['CreateTicket'],
    ++);
    ++
    ++
    ++my $eid = 0;
    ++for my $usage (qw/signed encrypted signed&encrypted/) {
    ++    for my $attachment (qw/plain text-attachment binary-attachment/) {
    ++        ++$eid;
    ++        diag "Email $eid: $usage, $attachment email" if $ENV{TEST_VERBOSE};
    ++        eval { email_ok($eid, $usage, $attachment) };
    ++    }
    ++}
    ++
    ++undef $m;
    ++done_testing;
    ++
    ++sub email_ok {
    ++    my ($eid, $usage, $attachment) = @_;
    ++    diag "email_ok $eid: $usage, $attachment" if $ENV{'TEST_VERBOSE'};
    ++
    ++    my ($file) = glob("$mails/$eid-*");
    ++    my $mail = RT::Test->file_content($file);
    ++
    ++    my ($status, $id) = RT::Test->send_via_mailgate($mail);
    ++    is ($status >> 8, 0, "$eid: The mail gateway exited normally");
    ++    ok ($id, "$eid: got id of a newly created ticket - $id");
    ++
    ++    my $tick = RT::Ticket->new( $RT::SystemUser );
    ++    $tick->Load( $id );
    ++    ok ($tick->id, "$eid: loaded ticket #$id");
    ++
    ++    is ($tick->Subject,
    ++        "Test Email ID:$eid",
    ++        "$eid: Created the ticket"
    ++    );
    ++
    ++    my $txn = $tick->Transactions->First;
    ++    my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
    ++
    ++    is( $msg->GetHeader('X-RT-Privacy'),
    ++        'SMIME',
    ++        "$eid: recorded incoming mail that is secured"
    ++    );
    ++
    ++    if ($usage =~ /encrypted/) {
    ++        is( $msg->GetHeader('X-RT-Incoming-Encryption'),
    ++            'Success',
    ++            "$eid: recorded incoming mail that is encrypted"
    ++        );
    ++        like( $attachments[0]->Content, qr/ID:$eid/,
    ++                "$eid: incoming mail did NOT have original body"
    ++        );
    ++    }
    ++    else {
    ++        is( $msg->GetHeader('X-RT-Incoming-Encryption'),
    ++            'Not encrypted',
    ++            "$eid: recorded incoming mail that is not encrypted"
    ++        );
    ++        like( $msg->Content || $attachments[0]->Content, qr/ID:$eid/,
    ++            "$eid: got original content"
    ++        );
    ++    }
    ++
    ++    if ($usage =~ /signed/) {
    ++        is( $msg->GetHeader('X-RT-Incoming-Signature'),
    ++            '"sender" <sender at example.com>',
    ++            "$eid: recorded incoming mail that is signed"
    ++        );
    ++    }
    ++    else {
    ++        is( $msg->GetHeader('X-RT-Incoming-Signature'),
    ++            undef,
    ++            "$eid: recorded incoming mail that is not signed"
    ++        );
    ++    }
    ++
    ++    if ($attachment =~ /attachment/) {
    ++        my ($a) = grep $_->Filename, @attachments;
    ++        ok ($a && $a->Id, "$eid: found attachment with filename");
    ++
    ++        my $acontent = $a->Content;
    ++        if ($attachment =~ /binary/)
    ++        {
    ++            is(md5_hex($acontent), '1e35f1aa90c98ca2bab85c26ae3e1ba7', "$eid: The binary attachment's md5sum matches");
    ++        }
    ++        else
    ++        {
    ++            like($acontent, qr/zanzibar/, "$eid: The attachment isn't screwed up in the database.");
    ++        }
    ++    }
    ++
    ++    return 0;
    ++}
    ++
    +
 80:  d1e0f7d < ---:  ------- move IO::Handler::CRLF
 81:  7c61dd5 < ---:  ------- refactor SignEncrypt to allow independant actions
 82:  5acece4 < ---:  ------- both signature formats can be handled in the same way
 83:  90aeafa < ---:  ------- line ends should be \x0D\x0A
 84:  7224999 < ---:  ------- clients can use application/x-pkcs7-signature or application/pkcs7-signature
 85:  5fceb54 < ---:  ------- fix number of tests and signer idententity
 87:  6c12fc9 < ---:  ------- ParsePKCS7Info - to figure out who signed a message
 88:  08194fd < ---:  ------- FormatStatus function instead of composing string all the time
 89:  56ddf9a < ---:  ------- check that key file exists beforehead
 90:  d0e80c0 < ---:  ------- message extracting exact option and postprocessing
 91:  9b10374 < ---:  ------- figure out signer of the message
 92:  e9bc260 < ---:  ------- fix cofiguration and emails in tests
 93:  55e5a84 < ---:  ------- we can not test with old files, new set of keys
 94:  12bc439 < ---:  ------- remove old files,we don't have keys for them
 95:  6687eca !  95:  01d750d Always pass Top entity when we detecting crypto parts
    @@ -1,6 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    always pass top entity when we detecting crypto parts
    +    Always pass Top entity when we detecting crypto parts
     
     diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
     --- a/lib/RT/Crypt.pm
    @@ -11,10 +11,8 @@
      
     +    $args{'TopEntity'} ||= $entity;
     +
    -     my @protocols = $args{'Protocols'}
    -         ? @{ $args{'Protocols'} } 
    -         : $self->EnabledOnIncoming;
    -         
    +     my @protocols = $self->EnabledOnIncoming;
    + 
          foreach my $protocol ( @protocols ) {
              my $class = $self->LoadImplementation( $protocol );
     -        my %info = $class->CheckIfProtected( Entity => $entity );
    @@ -26,11 +24,17 @@
      
              $args{'Skip'}{ $entity } = 1;
     @@
    + 
              foreach my $protocol ( @protocols ) {
                  my $class = $self->LoadImplementation( $protocol );
    -             my @list = $class->FindScatteredParts(
    +-            my @list = $class->FindScatteredParts( Parts => \@parts, Parents => \%parent, Skip => $args{'Skip'} );
    ++            my @list = $class->FindScatteredParts(
     +                Entity  => $args{'TopEntity'},
    -                 Parts   => \@parts,
    -                 Parents => \%parent,
    -                 Skip    => $args{'Skip'}
    ++                Parts   => \@parts,
    ++                Parents => \%parent,
    ++                Skip    => $args{'Skip'}
    ++            );
    +             next unless @list;
    + 
    +             $_->{'Protocol'} = $protocol foreach @list;
     
 96:  b8a3e4f < ---:  ------- when key not found, $res{info} is empty
 97:  2f4e71a !  96:  a98efb3 SMIME: Improve recipient detection by examining all possibilities
    @@ -1,9 +1,10 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    improve recipient detection for SMIME
    +    SMIME: Improve recipient detection by examining all possibilities
         
    -    check all recipients from message head, as well every address related
    -    to the queue, use someheuristicto guess most likely candidate
    +    Check all recipients from the message's header, as well as every address
    +    related to the queue.  If none are found, store the failure in the
    +    status.
     
     diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
     --- a/lib/RT/Crypt/SMIME.pm
    @@ -12,66 +13,48 @@
      
          my $msg = $args{'Data'}->as_string;
      
    -+    my %addresses;
    -+    $addresses{lc $_}++ foreach
    -+        map $_->address,
    -+        map Email::Address->parse($_),
    -+        @{$args{'Recipients'}};
    -+
    -     my $action = 'correspond';
    -     $action = 'comment' if grep defined && $_ eq 'comment', @{ $args{'Actions'}||[] };
    --
    +-    my $action = 'correspond';
    +-    $action = 'comment' if grep defined && $_ eq 'comment', @{ $args{'Actions'}||[] };
    ++    push @{ $args{'Recipients'} ||= [] },
    ++        $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
    ++        $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
    ++    ;
    + 
     -    my $address = $action eq 'correspond'
     -        ? $args{'Queue'}->CorrespondAddress || RT->Config->Get('CorrespondAddress')
     -        : $args{'Queue'}->CommentAddress    || RT->Config->Get('CommentAddress');
    --    my $key_file = File::Spec->catfile( 
    --        RT->Config->Get('SMIME')->{'Keyring'}, $address .'.pem'
    --    );
    --    unless ( -e $key_file && -r _ ) {
    --        $res{'exit_code'} = 1;
    --        $res{'status'} = $self->FormatStatus({
    --            Operation => 'KeyCheck',
    --            Status    => 'MISSING',
    --            Message   => "Secret key for '$address' is not available",
    + 
    +-    my %res;
    +-    my $file = $self->CheckKeyring( Key => $address );
    +-    unless ($file) {
    +-        $res{'status'} .= $self->FormatStatus({
    +-            Operation => "KeyCheck", Status => "MISSING",
    +-            Message   => "Secret key for $address is not available",
     -            Key       => $address,
    --            KeyType   => 'secret',
    +-            KeyType   => "secret",
     -        });
    --        $res{'User'} = {
    --            String => $address,
    --            SecretKeyMissing => 1,
    --        };
    +-        $res{exit_code} = 1;
     -        return %res;
    -+    if ( $action eq 'correspond' ) {
    -+        my $i = 1;
    -+        $addresses{lc $_} += $i++ foreach (
    -+            $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
    -+            $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
    -+        );
    -+    } else {
    -+        my $i = 1;
    -+        $addresses{lc $_} += $i++ foreach (
    -+            $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
    -+            $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress'),
    -+        );
    -     }
    -+    my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
    +-    }
    ++    my %seen;
    ++    my @addresses =
    ++        grep !$seen{lc $_}++, map $_->address, map Email::Address->parse($_),
    ++        grep length && defined, @{$args{'Recipients'}};
    ++
    ++    my ($buf, $encrypted_to, %res);
    ++
    ++    foreach my $address ( @addresses ) {
    ++        my $file = $self->CheckKeyring( Key => $address );
    ++        next unless $file;
      
    -     my $buf;
    +-    my $buf;
     -    {
    -+    my $found_key = 0;
    -+    my $encrypted_to;
    -+    foreach my $address ( sort { $addresses{$b} <=> $addresses{$a} } grep length, keys %addresses ) {
    -+        my $key_file = File::Spec->catfile( $keyring, $address .'.pem' );
    -+        next unless -e $key_file && -r _;
    -+
    -+        $found_key = 1;
    -+
              local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address );
              local $SIG{CHLD} = 'DEFAULT';
    -         my $cmd = join( ' ', shell_quote(
    +         my $cmd = [
     @@
    -             -recip => $key_file,
    -         ) );
    +                 : (),
    +         ];
              safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
     +        unless ( $? ) {
     +            $encrypted_to = $address;
    @@ -83,7 +66,7 @@
              $res{'exit_code'} = $?;
     -    }
     -    if ( $res{'exit_code'} ) {
    -         $res{'message'} = "openssl exitted with error code ". ($? >> 8)
    +         $res{'message'} = "openssl exited with error code ". ($? >> 8)
                  ." and error: $res{stderr}";
     +        $RT::Logger->error( $res{'message'} );
              $res{'status'} = $self->FormatStatus({
    @@ -93,7 +76,7 @@
              });
              return %res;
          }
    -+    unless ( $found_key ) {
    ++    unless ( $encrypted_to ) {
     +        $res{'exit_code'} = 1;
     +        $res{'status'} = $self->FormatStatus({
     +            Operation => 'KeyCheck',
    @@ -104,7 +87,7 @@
     +        return %res;
     +    }
      
    -     my $res_entity = _extract_msg_from_buf( \$buf, 1 );
    +     my $res_entity = _extract_msg_from_buf( \$buf );
          $res_entity->make_multipart( 'mixed', Force => 1 );
     @@
          $res{'status'} = $self->FormatStatus({
    @@ -116,11 +99,8 @@
      
          return %res;
     @@
    -                 }
    -             }
              }
    --        return () if !$security_type && $type eq 'application/octet-stream';
    -+        return () unless $security_type;
    +         return () unless $security_type;
      
     -        return (
     +        my %res = (
 98:  94e4352 < ---:  ------- upgrade script
 99:  44ecb5c < ---:  ------- fix number of tests
100:  9b6cdf8 < ---:  ------- make protocol case insensetive
101:  78b2d70 < ---:  ------- SMIME documentation
102:  9fd7325 < ---:  ------- this check is separate from the gpg check
103:  323ec0e < ---:  ------- autocreate a User custom field
104:  f8e1deb < ---:  ------- extract users' certificates out of signed messages
105:  127ec6b < ---:  ------- update description of the %Crypt hash option
106:  72d975c < ---:  ------- Don't set GnuPG explicitly as the protocol used for outoging emails
108:  7c68ae0 < ---:  ------- users' private keys are only supported for GnuPG
109:  5f8c9bf < ---:  ------- variable name typo
110:  7d4b4fd < ---:  ------- use Auth::Crypt mail plugin in tests
111:  d1ab193 < ---:  ------- privacy header was changed to match protocol
112:  1c95713 < ---:  ------- some programs use x-pkcs7-... and some use just pkcs7-...
113:  c0422d7 < ---:  ------- todo file for SMIME integration
114:  57322d9 < ---:  ------- delete backup files openssl creates in CA directory
115:  a687ed1 < ---:  ------- for simplicity allow non-unique subjects
116:  562fb32 < ---:  ------- update expiration date on SMIME certs
117:  e029cee < ---:  ------- ignore a few smime files CA operations create
118:  1f3098b < ---:  ------- accept user in RT::Test->import_smime_key
119:  12ed5db < ---:  ------- add_mail_catcher was dropped, we always catch mail in tests
120:  7d434a9 < ---:  ------- use root at example.com for testing as he has a key
121:  aaf357a < ---:  ------- use new feature in ->import_smime_key
122:  f6e9449 < ---:  ------- follow pattern in testing result of send_via_mailgate
123:  ee97826 < ---:  ------- Create the SMIME key CF
124:  a6abdd9 < ---:  ------- Correct the test count in the SMIME tests
125:  847d2ea < ---:  ------- Fill in SMIME docs in config and mail plugin
126:  75010ea < ---:  ------- drop mail catcher call, we always set it
127:  52dfe44 < ---:  ------- fix smime web tests
128:  4fce17a < ---:  ------- run more tests that are deeper
129:  e5a05ba < ---:  ------- first pass at generic attachments encrypt/decrypt in DB
130:  294626f < ---:  ------- Defend against undefined configurations when checking crypt protocols
---:  ------- >  97:  b7c47ef SMIME: Be more verbose on how it looks for, and fails to find, private keys
139:  a424932 !  98:  30be6a5 SMIME: Encrypting and decrypting attachments in the database
    @@ -1,16 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    encrypting/decrypting attachments in DB with SMIME
    -
    -diff --git a/TODO.SMIME b/TODO.SMIME
    ---- a/TODO.SMIME
    -+++ b/TODO.SMIME
    -@@
    --* port RT::Attachment::{Encrypt,Decrypt} over new API with SMIME support
    --
    - * continue with share//html/Ticket/Elements/ShowGnuPGStatus
    - 
    - * work harder on re-verification
    +    SMIME: Encrypting and decrypting attachments in the database
     
     diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
     --- a/lib/RT/Attachment.pm
    @@ -35,7 +25,7 @@
     +        Recipients => \@addresses,
     +    );
          if ( $res{'exit_code'} ) {
    -         return (0, $self->loc('GnuPG error. Contact with administrator'));
    +         return (0, $self->loc('Decryption error; contact the administrator'));
          }
     
     diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
    @@ -106,11 +96,8 @@
          my @keys;
          if ( $args{'Encrypt'} ) {
     -        my @addresses =
    --            map $_->address,
    --            map Email::Address->parse($_),
    --            grep defined && length,
    --            map $entity->head->get($_),
    --            qw(To Cc Bcc);
    +-            grep !$seen{$_}++, map $_->address, map Email::Address->parse($_),
    +-            grep defined && length, map $entity->head->get($_), qw(To Cc Bcc);
     +        my @addresses = @{ $args{'Recipients'} };
      
              foreach my $address ( @addresses ) {
    @@ -140,7 +127,9 @@
                  \$buf, \$err
              ) };
          }
    -     $RT::Logger->debug( "openssl stderr: " . $err ) if length $err;
    +@@
    +         }) if $args{'Encrypt'};
    +     }
      
     -    my $tmpdir = File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 );
     -    my $parser = MIME::Parser->new();
    @@ -151,50 +140,23 @@
     -    $entity->make_singlepart;
     -
     -    return %res;
    +-}
    +-
    +-sub SignEncryptContent {
    +-    my $self = shift;
    +-    return ( exit_code => 1 );
     +    return (\$buf, %res);
      }
      
      sub VerifyDecrypt {
     @@
    -     my $self = shift;
    -     my %args = (Data => undef, Queue => undef, @_ );
    - 
    --    my %res;
    --
    -     my $msg = $args{'Data'}->as_string;
    - 
    --    my %addresses;
    --    $addresses{lc $_}++ foreach
    --        map $_->address,
    --        map Email::Address->parse($_),
    --        @{$args{'Recipients'}};
    --
    --    my $action = 'correspond';
    --    $action = 'comment' if grep defined && $_ eq 'comment', @{ $args{'Actions'}||[] };
    --    if ( $action eq 'correspond' ) {
    --        my $i = 1;
    --        $addresses{lc $_} += $i++ foreach (
    --            $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
    --            $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
    --        );
    --    } else {
    --        my $i = 1;
    --        $addresses{lc $_} += $i++ foreach (
    --            $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
    --            $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress'),
    --        );
    --    }
    --    my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
    -+    push @{ $args{'Recipients'} ||= [] },
    -+        $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
    -+        $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
    -+    ;
    - 
    --    my $buf;
    +         $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
    +     ;
    + 
     +    my ($buf, %res) = $self->_Decrypt( %args, Content => \$args{'Data'}->as_string );
     +    return %res unless $buf;
     +
    -+    my $res_entity = _extract_msg_from_buf( $buf, 1 );
    ++    my $res_entity = _extract_msg_from_buf( $buf );
     +    $res_entity->make_multipart( 'mixed', Force => 1 );
     +
     +    $args{'Data'}->make_multipart( 'mixed', Force => 1 );
    @@ -219,31 +181,18 @@
     +sub _Decrypt {
     +    my $self = shift;
     +    my %args = (Content => undef, @_ );
    -+
    -+    my %seen;
    -+    my @addresses =
    -+        grep !$seen{lc $_}++, map $_->address, map Email::Address->parse($_),
    -+        grep length && defined, @{$args{'Recipients'}};
    -+
    -+    my ($buf, $encrypted_to, %res);
    -+
    -+    my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
    -     my $found_key = 0;
    --    my $encrypted_to;
    --    foreach my $address ( sort { $addresses{$b} <=> $addresses{$a} } grep length, keys %addresses ) {
    -+    foreach my $address ( @addresses ) {
    -         my $key_file = File::Spec->catfile( $keyring, $address .'.pem' );
    -         next unless -e $key_file && -r _;
    - 
    + 
    +     my %seen;
    +     my @addresses =
     @@
                      ? (qw(-passin env:SMIME_PASS))
                      : (),
    -         ) );
    +         ];
     -        safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
     +        safe_run_child { run3( $cmd, $args{'Content'}, \$buf, \$res{'stderr'} ) };
              unless ( $? ) {
                  $encrypted_to = $address;
    -             last;
    +             $RT::Logger->debug("Message encrypted for $encrypted_to");
     @@
                  Message => 'Decryption failed',
                  EncryptedTo => $address,
    @@ -251,8 +200,8 @@
     -        return %res;
     +        return (undef, %res);
          }
    -     unless ( $found_key ) {
    -         $res{'exit_code'} = 1;
    +     unless ( $encrypted_to ) {
    +         $RT::Logger->error("Couldn't find SMIME key for addresses: ". join ', ', @addresses);
     @@
                  Message   => "Secret key is not available",
                  KeyType   => 'secret',
    @@ -261,7 +210,7 @@
     +        return (undef, %res);
          }
      
    --    my $res_entity = _extract_msg_from_buf( \$buf, 1 );
    +-    my $res_entity = _extract_msg_from_buf( \$buf );
     -    $res_entity->make_multipart( 'mixed', Force => 1 );
     -
     -    $args{'Data'}->make_multipart( 'mixed', Force => 1 );
    @@ -275,8 +224,64 @@
          });
      
     -    return %res;
    +-}
    +-
    +-sub DecryptContent {
    +-    my $self = shift;
    +-    return ( exit_code => 1 );
     +    return (\$buf, %res);
      }
      
      sub FormatStatus {
     
    +diff --git a/t/crypt/smime/attachments-in-db.t b/t/crypt/smime/attachments-in-db.t
    +new file mode 100644
    +--- /dev/null
    ++++ b/t/crypt/smime/attachments-in-db.t
    +@@
    ++use strict;
    ++use warnings;
    ++
    ++use RT::Test::SMIME tests => undef;
    ++
    ++use IPC::Run3 'run3';
    ++use String::ShellQuote 'shell_quote';
    ++use RT::Tickets;
    ++
    ++RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'} = 1;
    ++
    ++RT::Test::SMIME->import_key('sender at example.com');
    ++my $queue = RT::Test->load_or_create_queue(
    ++    Name              => 'General',
    ++    CorrespondAddress => 'sender at example.com',
    ++);
    ++ok $queue && $queue->id, 'loaded or created queue';
    ++
    ++{
    ++    my $ticket = RT::Test->create_ticket(
    ++        Queue   => $queue->id,
    ++        Subject => 'test',
    ++        Content => 'test',
    ++    );
    ++
    ++    my $txn = $ticket->Transactions->First;
    ++    ok $txn && $txn->id, 'found first transaction';
    ++    is $txn->Type, 'Create', 'it is Create transaction';
    ++
    ++    my $attach = $txn->Attachments->First;
    ++    ok $attach && $attach->id, 'found attachment';
    ++    is $attach->Content, 'test', 'correct content';
    ++
    ++    my ($status, $msg) = $attach->Encrypt;
    ++    ok $status, 'encrypted attachment' or diag "error: $msg";
    ++
    ++    isnt $attach->Content, 'test', 'correct content';
    ++
    ++    ($status, $msg) = $attach->Decrypt;
    ++    ok $status, 'decrypted attachment' or diag "error: $msg";
    ++
    ++    is $attach->Content, 'test', 'correct content';
    ++}
    ++
    ++done_testing;
    +
---:  ------- >  99:  5186f9a Upgrade script for users of RT::Extension::SMIME and ::Crypt
131:  16c4f4a ! 100:  b1ad243 Factor out sending templated errors for convenient future use
    @@ -1,6 +1,6 @@
     Author: Jason May <jasonmay at bestpractical.com>
     
    -    Factor sending templated errors for convenient future use
    +    Factor out sending templated errors for convenient future use
     
     diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
     --- a/lib/RT/Interface/Email/Auth/Crypt.pm
    @@ -35,13 +35,12 @@
      
     +    return EmailErrorToSender(
     +        %args,
    -+        Template  => 'Error: bad GnuPG data',
    ++        Template  => 'Error: bad encrypted data',
     +        Arguments => { Messages  => [ @bad_data_messages ] },
     +    );
     +}
     +
     +sub EmailErrorToSender {
    -+    my $self = shift;
     +    my %args = (@_);
     +
     +    $args{'Arguments'} ||= {};
    @@ -50,7 +49,7 @@
          my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
          my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
              To        => $address,
    --        Template  => 'Error: bad GnuPG data',
    +-        Template  => 'Error: bad encrypted data',
     -        Arguments => {
     -            Messages  => [ @bad_data_messages ],
     -            TicketObj => $args{'Ticket'},
    @@ -60,4 +59,9 @@
              InReplyTo => $args{'Message'},
          );
          unless ( $status ) {
    +-        $RT::Logger->error("Couldn't send 'Error: bad encrypted data'");
    ++        $RT::Logger->error("Couldn't send '$args{'Template'}''");
    +     }
    +     return 0;
    + }
     
132:  d6b0e3d < ---:  ------- Factor sending templated errors for convenient future use
133:  54e3666 < ---:  ------- Error and abort if an unencrypted message is received in Strict mode
134:  95fbd5b ! 101:  f7e861a Add RejectOnUnencrypted to force all incoming messages to be encrypted
    @@ -1,90 +1,119 @@
    -Author: Jason May <jasonmay at bestpractical.com>
    -
    -    Test sending (un)encrypted mail with strict auth enabled
    -
    -diff --git a/t/mail/smime/strict.t b/t/mail/smime/strict.t
    +Author: Alex Vandiver <alexmv at bestpractical.com>
    +
    +    Add RejectOnUnencrypted to force all incoming messages to be encrypted
    +
    +diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
    +--- a/etc/RT_Config.pm.in
    ++++ b/etc/RT_Config.pm.in
    +@@
    + be used in outgoing emails.  At this moment, only one protocol can be
    + used to protect outgoing emails.
    + 
    ++Set C<RejectOnUnencrypted> to true if all incoming email must be
    ++properly encrypted.  All unencrypted emails will be rejected by RT.
    ++
    + Set C<RejectOnMissingPrivateKey> to false if you don't want to reject
    + emails encrypted for key RT doesn't have and can not decrypt.
    + 
    +@@
    +     Incoming                  => undef, # ['GnuPG', 'SMIME']
    +     Outgoing                  => undef, # 'SMIME'
    + 
    ++    RejectOnUnencrypted       => 0,
    +     RejectOnMissingPrivateKey => 1,
    +     RejectOnBadData           => 1,
    + 
    +
    +diff --git a/etc/initialdata b/etc/initialdata
    +--- a/etc/initialdata
    ++++ b/etc/initialdata
    +@@
    + }
    +     },
    +     {  Queue       => 0,
    ++       Name        => "Error: unencrypted message",    # loc
    ++       Description =>
    ++         "Inform user that their unencrypted mail has been rejected", # loc
    ++       Content => q{Subject: RT requires that all incoming mail be encrypted
    ++
    ++You received this message because RT received mail from you that was not encrypted.  As such, it has been rejected.
    ++}
    ++    },
    ++    {  Queue       => 0,
    +        Name        => "Error: public key",    # loc
    +        Description =>
    +          "Inform user that he has problems with public key and couldn't recieve encrypted content", # loc
    +
    +diff --git a/etc/upgrade/4.1.20/content b/etc/upgrade/4.1.20/content
    +--- a/etc/upgrade/4.1.20/content
    ++++ b/etc/upgrade/4.1.20/content
    +@@
    +         return 1;
    +     },
    + );
    ++
    ++our @Templates = (
    ++    {  Queue       => 0,
    ++       Name        => "Error: unencrypted message",    # loc
    ++       Description =>
    ++         "Inform user that their unencrypted mail has been rejected", # loc
    ++       Content => q{Subject: RT requires that all incoming mail be encrypted
    ++
    ++You received this message because RT received mail from you that was not encrypted.  As such, it has been rejected.
    ++}
    ++    },
    ++);
    +
    +diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
    +--- a/lib/RT/Interface/Email/Auth/Crypt.pm
    ++++ b/lib/RT/Interface/Email/Auth/Crypt.pm
    +@@
    +         Entity => $args{'Message'},
    +     );
    +     if ( !@res ) {
    +-        $args{'Message'}->head->replace(
    +-            'X-RT-Incoming-Encryption' => 'Not encrypted'
    +-        );
    +-
    ++        if (RT->Config->Get('Crypt')->{'RejectOnUnencrypted'}) {
    ++            EmailErrorToSender(
    ++                %args,
    ++                Template  => 'Error: unencrypted message',
    ++                Arguments => { Message  => $args{'Message'} },
    ++            );
    ++            return (-1, 'rejected because the message is unencrypted with RejectOnUnencrypted enabled');
    ++        }
    ++        else {
    ++            $args{'Message'}->head->replace(
    ++                'X-RT-Incoming-Encryption' => 'Not encrypted'
    ++            );
    ++        }
    +         return 1;
    +     }
    + 
    +
    +diff --git a/t/mail/smime/reject_on_unencrypted.t b/t/mail/smime/reject_on_unencrypted.t
     new file mode 100644
     --- /dev/null
    -+++ b/t/mail/smime/strict.t
    -@@
    -+#!/usr/bin/perl
    ++++ b/t/mail/smime/reject_on_unencrypted.t
    +@@
     +use strict;
     +use warnings;
     +
    -+use RT::Test tests => 22;
    -+
    -+my $openssl = RT::Test->find_executable('openssl');
    -+plan skip_all => 'openssl executable is required.'
    -+    unless $openssl;
    ++use RT::Test::SMIME tests => undef;
    ++my $test = 'RT::Test::SMIME';
     +
     +use IPC::Run3 'run3';
     +use String::ShellQuote 'shell_quote';
     +use RT::Tickets;
     +
    -+my $keys = RT::Test::get_abs_relocatable_dir(
    -+    (File::Spec->updir()) x 2,
    -+    qw(data smime keys),
    -+);
    -+
    -+my $keyring = RT::Test->new_temp_dir(
    -+    crypt => smime => 'smime_keyring'
    -+);
    -+
    -+RT->Config->Set( Crypt =>
    -+    Enable   => 1,
    -+    Strict   => 1,
    -+    Incoming => ['SMIME'],
    -+    Outgoing => 'SMIME',
    -+);
    -+RT->Config->Set( GnuPG => Enable => 0 );
    -+RT->Config->Set( SMIME =>
    -+    Enable => 1,
    -+    Strict => 1,
    -+    OutgoingMessagesFormat => 'RFC',
    -+    Passphrase => {
    -+        'sender at example.com' => '123456',
    -+    },
    -+    OpenSSL => $openssl,
    -+    Keyring => $keyring,
    -+);
    -+
    -+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
    -+
    -+{
    -+    my $cf = RT::CustomField->new( $RT::SystemUser );
    -+    my ($ret, $msg) = $cf->Create(
    -+        Name       => 'SMIME Key',
    -+        LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
    -+        Type       => 'TextSingle',
    -+    );
    -+    ok($ret, "Custom Field created");
    -+
    -+    my $OCF = RT::ObjectCustomField->new( $RT::SystemUser );
    -+    $OCF->Create(
    -+        CustomField => $cf->id,
    -+        ObjectId    => 0,
    -+    );
    -+}
    -+
    -+{
    -+    my $template = RT::Template->new($RT::SystemUser);
    -+    $template->Create(
    -+        Name => 'NotEncryptedMessage',
    -+        Queue => 0,
    -+        Content => <<EOF,
    -+
    -+Subject: Failed to send unencrypted message
    -+
    -+This message was not sent since it is unencrypted:
    -+EOF
    -+    );
    -+}
    ++RT->Config->Get('Crypt')->{'RejectOnUnencrypted'} = 1;
     +
     +my ($url, $m) = RT::Test->started_ok;
     +ok $m->login, "logged in";
     +
     +# configure key for General queue
    -+RT::Test->import_smime_key('sender at example.com');
    ++RT::Test::SMIME->import_key('sender at example.com');
     +my $queue = RT::Test->load_or_create_queue(
     +    Name              => 'General',
     +    CorrespondAddress => 'sender at example.com',
    @@ -96,7 +125,7 @@
     +    Name => 'root at example.com',
     +    EmailAddress => 'root at example.com',
     +);
    -+RT::Test->import_smime_key('root at example.com.crt', $user);
    ++RT::Test::SMIME->import_key('root at example.com.crt', $user);
     +RT::Test->add_rights( Principal => $user, Right => 'SuperUser', Object => RT->System );
     +
     +my $mail = RT::Test->open_mailgate_ok($url);
    @@ -115,11 +144,11 @@
     +    my ($mail) = RT::Test->fetch_caught_mails;
     +    like(
     +        $mail,
    -+        qr/^Subject: Failed to send unencrypted message/m,
    -+        'recorded incoming mail that is not encrypted'
    ++        qr/^Subject: RT requires that all incoming mail be encrypted/m,
    ++        'rejected mail that is not encrypted'
     +    );
     +    my ($warning) = $m->get_warnings;
    -+    like($warning, qr/rejected because the message is unencrypted with Strict mode enabled/);
    ++    like($warning, qr/rejected because the message is unencrypted/);
     +}
     +
     +{
    @@ -129,9 +158,9 @@
     +        shell_quote(
     +            qw(openssl smime -encrypt  -des3),
     +            -from    => 'root at example.com',
    -+            -to      => 'rt@' . $RT::rtname,
    ++            -to      => 'sender at example.com',
     +            -subject => "Encrypted message for queue",
    -+            File::Spec->catfile( $keys, 'sender at example.com.crt' ),
    ++            $test->key_path('sender at example.com.crt' ),
     +        ),
     +        \"Subject: test\n\norzzzzzz",
     +        \$buf,
    @@ -171,16 +200,16 @@
     +            shell_quote(
     +                RT->Config->Get('SMIME')->{'OpenSSL'},
     +                qw( smime -sign -nodetach -passin pass:123456),
    -+                -signer => File::Spec->catfile( $keys, 'root at example.com.crt' ),
    -+                -inkey  => File::Spec->catfile( $keys, 'root at example.com.key' ),
    ++                -signer => $test->key_path('root at example.com.crt' ),
    ++                -inkey  => $test->key_path('root at example.com.key' ),
     +            ),
     +            '|',
     +            shell_quote(
     +                qw(openssl smime -encrypt -des3),
     +                -from    => 'root at example.com',
    -+                -to      => 'rt@' . RT->Config->Get('rtname'),
    ++                -to      => 'sender at example.com',
     +                -subject => "Encrypted and signed message for queue",
    -+                File::Spec->catfile( $keys, 'sender at example.com.crt' ),
    ++                $test->key_path('sender at example.com.crt' ),
     +            )),
     +            \"Subject: test\n\norzzzzzz",
     +            \$buf,
    @@ -205,4 +234,6 @@
     +    like( $attach->Content, qr'orzzzz');
     +}
     +
    -
    ++undef $m;
    ++done_testing;
    +
168:  cb77896 ! 102:  70a3f5a Allow encryption/signing of dashboards
    @@ -1,8 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    make it possible to encrypt/sign dashboards
    -    
    -    an option in %Crypt config
    +    Allow encryption/signing of dashboards
     
     diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
     --- a/etc/RT_Config.pm.in
136:  caa078b ! 103:  8982b44 Refactor common delegation code
    @@ -1,14 +1,22 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    refactor common code
    +    Refactor common delegation code
     
     diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
     --- a/lib/RT/Crypt.pm
     +++ b/lib/RT/Crypt.pm
     @@
    -     return $class;
    - } }
    +     }
    + }
      
    ++=head2 SimpleImplementationCall Protocol => NAME, [...]
    ++
    ++Examines the caller of this method, and dispatches to the method of the
    ++same name on the correct L<RT::Crypt::Role> class based on the provided
    ++C<Protocol>.
    ++
    ++=cut
    ++
     +sub SimpleImplementationCall {
     +    my $self = shift;
     +    my %args = (@_);
    @@ -22,9 +30,9 @@
     +    return %res;
     +}
     +
    - # encryption and signatures can be nested one over another, for example:
    - # GPG inline signed text can be signed with SMIME
    + =head2 FindProtectedParts Entity => MIME::Entity
      
    + Looks for encrypted or signed parts of the given C<Entity>, using all
     @@
                  qw(To Cc Bcc)
              ];
    @@ -37,19 +45,21 @@
     +    return $self->SimpleImplementationCall( %args );
      }
      
    - sub SignEncryptContent {
    + =head2 SignEncryptContent Content => STRINGREF, [Sign => 1], [Encrypt => 1],
     @@
              $args{'Recipients'} = [ RT->Config->Get('CorrespondAddress') ];
          }
      
     -    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
    --    my %res = $self->LoadImplementation( $protocol )->SignEncryptContent( %args );
    +-    my $class = $self->LoadImplementation( $protocol );
    +-
    +-    my %res = $class->SignEncryptContent( %args );
     -    $res{'Protocol'} = $protocol;
     -    return %res;
     +    return $self->SimpleImplementationCall( %args );
      }
      
    - sub DrySign {
    + =head2 DrySign Signer => KEY
     @@
      
          my @protected = $self->FindProtectedParts( Entity => $args{'Entity'} );
    @@ -58,30 +68,30 @@
     -        my $class = $self->LoadImplementation( $protocol );
     -        my %res = $class->VerifyDecrypt( %args, Info => $protected );
     -        $res{'Protocol'} = $protocol;
    --        push @res, \%res;
    -+        push @res, { $self->SimpleImplementationCall(
    ++        my %res = $self->SimpleImplementationCall(
     +            %args, Protocol => $protected->{'Protocol'}, Info => $protected
    -+        ) };
    -     }
    -     return @res;
    - }
    ++        );
    + 
    +         # Let the header be modified so continuations are handled
    +         my $modify = $res{status_on}->head->modify;
    +@@
    + =cut
      
      sub DecryptContent {
     -    my $self = shift;
    --    my %args = (@_);
    --
    --    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
    --    my %res = $self->LoadImplementation( $protocol )->DecryptContent( %args );
    --    $res{'Protocol'} = $protocol;
    --    return %res;
    +-    my %args = (
    +-        Protocol => undef,
    +-        @_
    +-    );
    +-    return $self->LoadImplementation( $args{'Protocol'} )->DecryptContent( @_ );
     +    return shift->SimpleImplementationCall( @_ );
      }
      
    - sub ParseStatus {
    + =head2 ParseStatus Protocol => NAME, Status => STRING
     @@
      sub GetKeysForEncryption {
          my $self = shift;
    -     my %args = @_%2? (Recipient => @_) : (Protocol => undef, For => undef, @_ );
    +     my %args = @_%2? (Recipient => @_) : (Protocol => undef, Recipient => undef, @_ );
     -    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
     -    my %res = $self->LoadImplementation( $protocol )->GetKeysForEncryption( %args );
     -    $res{'Protocol'} = $protocol;
    @@ -89,6 +99,8 @@
     +    return $self->SimpleImplementationCall( %args );
      }
      
    + =head2 GetKeysForSigning Signer => EMAIL, Protocol => NAME
    +@@
      sub GetKeysForSigning {
          my $self = shift;
          my %args = @_%2? (Signer => @_) : (Protocol => undef, Signer => undef, @_);
    @@ -99,7 +111,7 @@
     +    return $self->SimpleImplementationCall( %args );
      }
      
    - sub GetPublicKeyInfo {
    + =head2 GetPublicKeyInfo Protocol => NAME, KEY => EMAIL
     @@
      sub GetKeysInfo {
          my $self = shift;
137:  98eba5e < ---:  ------- use x-application-rt\/$protocol-encrypted content type
138:  e1bc3e0 < ---:  ------- Move AllowEncryptDataInDB into %Crypt option
140:  8830d2f < ---:  ------- extract common code into RT::Test::SMIME
141:  baac570 < ---:  ------- test encrypting attachments in DB with SMIME
142:  a6662f2 < ---:  ------- reference proper argument in loc string
143:  c12781e < ---:  ------- adjust number of tests
144:  ae59e43 < ---:  ------- return back some wording to make patching old RT easier
145:  d3c8ace < ---:  ------- move SMIME upgrade script from 3.8.8 to 4.1.0
---:  ------- > 104:  278b990 Only GnuPG supports multiple private keys per user; restrict PrivateKey
---:  ------- > 105:  7a25437 SMIME: Admin interface for updating SMIME keys
---:  ------- > 106:  4bdeb9d /Admin/Users/GnuPG.html is no longer just GPG, but all secret keys
146:  d3ba497 ! 107:  a100e31 Rename GnuPG mason components to Crypt
    @@ -1,6 +1,280 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    rename *GnuPG* components to *Crypt*
    +    Rename GnuPG mason components to Crypt
    +
    +diff --git a/share/html/Admin/Users/GnuPG.html b/share/html/Admin/Users/GnuPG.html
    +deleted file mode 100644
    +--- a/share/html/Admin/Users/GnuPG.html
    ++++ /dev/null
    +@@
    +-%# BEGIN BPS TAGGED BLOCK {{{
    +-%#
    +-%# COPYRIGHT:
    +-%#
    +-%# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
    +-%#                                          <sales at bestpractical.com>
    +-%#
    +-%# (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 }}}
    +-<& /Admin/Elements/Header, Title => loc("[_1]'s encryption keys",$UserObj->Name)  &>
    +-<& /Elements/Tabs &>
    +-
    +-<& /Elements/ListActions, actions => \@results &>
    +-
    +-% if ( $email ) {
    +-<& /Admin/Elements/ShowKeyInfo, EmailAddress => $email &>
    +-% } else {
    +-<h2><% loc("User has empty email address") %></h2>
    +-% }
    +-
    +-<form action="<%RT->Config->Get('WebPath')%>/Admin/Users/GnuPG.html" method="post" enctype="multipart/form-data">
    +-<input type="hidden" class="hidden" name="id" value="<% $UserObj->Id %>" />
    +-
    +-% if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
    +-<&|/Widgets/TitleBox, title => 'GnuPG private key'&>
    +-<& /Widgets/Form/Select,
    +-    Name         => 'PrivateKey',
    +-    Description  => loc('Private Key'),
    +-    Values       => \@potential_keys,
    +-    CurrentValue => $UserObj->PrivateKey,
    +-    DefaultLabel => loc('No private key'),
    +-&>
    +-</&>
    +-% }
    +-
    +-% if (RT::Crypt->UseForOutgoing eq 'SMIME') {
    +-<&|/Widgets/TitleBox, title => 'SMIME Certificate' &>
    +-<textarea name="SMIMECertificate"><% $UserObj->SMIMECertificate || '' %></textarea>
    +-</&>
    +-% }
    +-
    +-<& /Elements/Submit, Name => 'Update', Label => loc('Save Changes') &>
    +-</form>
    +-
    +-<%ARGS>
    +-$id         => undef
    +-$Update     => undef
    +-</%ARGS>
    +-<%INIT>
    +-return unless RT->Config->Get('Crypt')->{'Enable'};
    +-
    +-my @results;
    +-
    +-my $UserObj = RT::User->new( $session{'CurrentUser'} );
    +-$UserObj->Load( $id );
    +-unless ( $UserObj->id ) {
    +-    Abort( loc("Couldn't load user #[_1]", $id) );
    +-}
    +-$id = $ARGS{'id'} = $UserObj->id;
    +-
    +-my @potential_keys;
    +-my $email = $UserObj->EmailAddress;
    +-
    +-if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
    +-    my %keys_meta = RT::Crypt->GetKeysForSigning( Signer => $email, Protocol => 'GnuPG' );
    +-    @potential_keys = map $_->{'Key'}, @{ $keys_meta{'info'} || [] };
    +-
    +-    $ARGS{'PrivateKey'} = $m->comp('/Widgets/Form/Select:Process',
    +-        Name      => 'PrivateKey',
    +-        Arguments => \%ARGS,
    +-        Default   => 1,
    +-    );
    +-
    +-    if ( $Update ) {
    +-        if (not $ARGS{'PrivateKey'} or grep {$_ eq $ARGS{'PrivateKey'}} @potential_keys) {
    +-            if (($ARGS{'PrivateKey'}||'') ne ($UserObj->PrivateKey||'')) {
    +-                my ($status, $msg) = $UserObj->SetPrivateKey( $ARGS{'PrivateKey'} );
    +-                push @results, $msg;
    +-            }
    +-        } else {
    +-            push @results, loc("Invalid key [_1] for address '[_2]'", $ARGS{'PrivateKey'}, $email);
    +-        }
    +-    }
    +-}
    +-
    +-if (RT::Crypt->UseForOutgoing eq 'SMIME') {
    +-    if ( $Update and ($ARGS{'SMIMECertificate'}||'') ne ($UserObj->SMIMECertificate||'') ) {
    +-        my ($status, $msg) = $UserObj->SetSMIMECertificate( $ARGS{'SMIMECertificate'} );
    +-        push @results, $msg;
    +-    }
    +-}
    +-
    +-</%INIT>
    +
    +diff --git a/share/html/Admin/Users/Keys.html b/share/html/Admin/Users/Keys.html
    +new file mode 100644
    +--- /dev/null
    ++++ b/share/html/Admin/Users/Keys.html
    +@@
    ++%# BEGIN BPS TAGGED BLOCK {{{
    ++%#
    ++%# COPYRIGHT:
    ++%#
    ++%# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
    ++%#                                          <sales at bestpractical.com>
    ++%#
    ++%# (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 }}}
    ++<& /Admin/Elements/Header, Title => loc("[_1]'s encryption keys",$UserObj->Name)  &>
    ++<& /Elements/Tabs &>
    ++
    ++<& /Elements/ListActions, actions => \@results &>
    ++
    ++% if ( $email ) {
    ++<& /Admin/Elements/ShowKeyInfo, EmailAddress => $email &>
    ++% } else {
    ++<h2><% loc("User has empty email address") %></h2>
    ++% }
    ++
    ++<form action="<%RT->Config->Get('WebPath')%>/Admin/Users/Keys.html" method="post" enctype="multipart/form-data">
    ++<input type="hidden" class="hidden" name="id" value="<% $UserObj->Id %>" />
    ++
    ++% if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
    ++<&|/Widgets/TitleBox, title => 'GnuPG private key'&>
    ++<& /Widgets/Form/Select,
    ++    Name         => 'PrivateKey',
    ++    Description  => loc('Private Key'),
    ++    Values       => \@potential_keys,
    ++    CurrentValue => $UserObj->PrivateKey,
    ++    DefaultLabel => loc('No private key'),
    ++&>
    ++</&>
    ++% }
    ++
    ++% if (RT::Crypt->UseForOutgoing eq 'SMIME') {
    ++<&|/Widgets/TitleBox, title => 'SMIME Certificate' &>
    ++<textarea name="SMIMECertificate"><% $UserObj->SMIMECertificate || '' %></textarea>
    ++</&>
    ++% }
    ++
    ++<& /Elements/Submit, Name => 'Update', Label => loc('Save Changes') &>
    ++</form>
    ++
    ++<%ARGS>
    ++$id         => undef
    ++$Update     => undef
    ++</%ARGS>
    ++<%INIT>
    ++return unless RT->Config->Get('Crypt')->{'Enable'};
    ++
    ++my @results;
    ++
    ++my $UserObj = RT::User->new( $session{'CurrentUser'} );
    ++$UserObj->Load( $id );
    ++unless ( $UserObj->id ) {
    ++    Abort( loc("Couldn't load user #[_1]", $id) );
    ++}
    ++$id = $ARGS{'id'} = $UserObj->id;
    ++
    ++my @potential_keys;
    ++my $email = $UserObj->EmailAddress;
    ++
    ++if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
    ++    my %keys_meta = RT::Crypt->GetKeysForSigning( Signer => $email, Protocol => 'GnuPG' );
    ++    @potential_keys = map $_->{'Key'}, @{ $keys_meta{'info'} || [] };
    ++
    ++    $ARGS{'PrivateKey'} = $m->comp('/Widgets/Form/Select:Process',
    ++        Name      => 'PrivateKey',
    ++        Arguments => \%ARGS,
    ++        Default   => 1,
    ++    );
    ++
    ++    if ( $Update ) {
    ++        if (not $ARGS{'PrivateKey'} or grep {$_ eq $ARGS{'PrivateKey'}} @potential_keys) {
    ++            if (($ARGS{'PrivateKey'}||'') ne ($UserObj->PrivateKey||'')) {
    ++                my ($status, $msg) = $UserObj->SetPrivateKey( $ARGS{'PrivateKey'} );
    ++                push @results, $msg;
    ++            }
    ++        } else {
    ++            push @results, loc("Invalid key [_1] for address '[_2]'", $ARGS{'PrivateKey'}, $email);
    ++        }
    ++    }
    ++}
    ++
    ++if (RT::Crypt->UseForOutgoing eq 'SMIME') {
    ++    if ( $Update and ($ARGS{'SMIMECertificate'}||'') ne ($UserObj->SMIMECertificate||'') ) {
    ++        my ($status, $msg) = $UserObj->SetSMIMECertificate( $ARGS{'SMIMECertificate'} );
    ++        push @results, $msg;
    ++    }
    ++}
    ++
    ++</%INIT>
     
     diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
     new file mode 100644
    @@ -163,7 +437,6 @@
     +% }
     +
     +<%INIT>
    -+require RT::Crypt::GnuPG;
     +my $d;
     +
     +my %res = RT::Crypt->GetKeysForEncryption($EmailAddress);
    @@ -185,6 +458,7 @@
     +$EmailAddress => undef
     +$Default      => undef
     +</%ARGS>
    ++
     
     diff --git a/share/html/Elements/Crypt/SelectKeyForSigning b/share/html/Elements/Crypt/SelectKeyForSigning
     new file mode 100644
    @@ -256,7 +530,7 @@
     +# XXX: Only GnuPG at this moment supports user's private keys
     +my $user_key;
     +$user_key = $User->PrivateKey
    -+    if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';;
    ++    if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';
     +</%INIT>
     
     diff --git a/share/html/Elements/Crypt/SignEncryptWidget b/share/html/Elements/Crypt/SignEncryptWidget
    @@ -614,7 +888,6 @@
     -% }
     -
     -<%INIT>
    --require RT::Crypt::GnuPG;
     -my $d;
     -
     -my %res = RT::Crypt->GetKeysForEncryption($EmailAddress);
    @@ -636,6 +909,7 @@
     -$EmailAddress => undef
     -$Default      => undef
     -</%ARGS>
    +-
     
     diff --git a/share/html/Elements/GnuPG/SelectKeyForSigning b/share/html/Elements/GnuPG/SelectKeyForSigning
     deleted file mode 100644
    @@ -707,7 +981,7 @@
     -# XXX: Only GnuPG at this moment supports user's private keys
     -my $user_key;
     -$user_key = $User->PrivateKey
    --    if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';;
    +-    if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';
     -</%INIT>
     
     diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
    @@ -973,7 +1247,7 @@
     +my @runs;
     +my $needs_unsigned_warning = $WarnUnsigned;
     +
    -+my @protocols = RT::Crypt->Protocols;
    ++my @protocols = RT::Crypt->EnabledProtocols;
     +my $re_protocols = join '|', map "\Q$_\E", @protocols;
     +
     +foreach ( $Attachment->SplitHeaders ) {
    @@ -1016,29 +1290,25 @@
     +        return (0, "Couldn't parse content of attachment #". $original->id);
     +    }
     +
    -+    use RT::Interface::Email::Auth::Crypt;
    -+    my ($status, @res) = RT::Interface::Email::Auth::Crypt::VerifyDecrypt( Entity => $entity );
    -+    if ( $status && !@res ) {
    -+        # imposible in this situation
    -+        return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted");
    -+    }
    -+    elsif ( @res ) {
    -+        require RT::Crypt;
    -+
    -+        $top->DelHeader('X-RT-GnuPG-Status');
    -+        $top->AddHeader(map { ('X-RT-GnuPG-Status' => $_->{'status'} ) } @res);
    -+        $top->SetHeader('X-RT-Privacy' => 'GnuPG' );
    -+        $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;
    -+            }
    ++    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");
    @@ -1164,7 +1434,7 @@
     -my @runs;
     -my $needs_unsigned_warning = $WarnUnsigned;
     -
    --my @protocols = RT::Crypt->Protocols;
    +-my @protocols = RT::Crypt->EnabledProtocols;
     -my $re_protocols = join '|', map "\Q$_\E", @protocols;
     -
     -foreach ( $Attachment->SplitHeaders ) {
    @@ -1207,29 +1477,25 @@
     -        return (0, "Couldn't parse content of attachment #". $original->id);
     -    }
     -
    --    use RT::Interface::Email::Auth::Crypt;
    --    my ($status, @res) = RT::Interface::Email::Auth::Crypt::VerifyDecrypt( Entity => $entity );
    --    if ( $status && !@res ) {
    --        # imposible in this situation
    --        return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted");
    --    }
    --    elsif ( @res ) {
    --        require RT::Crypt;
    --
    --        $top->DelHeader('X-RT-GnuPG-Status');
    --        $top->AddHeader(map { ('X-RT-GnuPG-Status' => $_->{'status'} ) } @res);
    --        $top->SetHeader('X-RT-Privacy' => 'GnuPG' );
    --        $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;
    --            }
    +-    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");
    @@ -1286,25 +1552,51 @@
     -
     -</%INIT>
     
    +diff --git a/share/html/Elements/ShowHistory b/share/html/Elements/ShowHistory
    +--- a/share/html/Elements/ShowHistory
    ++++ b/share/html/Elements/ShowHistory
    +@@
    +         UpdatePath      => 'Update.html',
    +         ForwardPath     => 'Forward.html',
    +         EmailRecordPath => 'ShowEmailRecord.html',
    +-        EncryptionPath  => 'GnuPG.html',
    ++        EncryptionPath  => 'Crypt.html',
    +     );
    + 
    +     while ( my ($arg, $path) = each %tmp ) {
    +
     diff --git a/share/html/Elements/ShowTransactionAttachments b/share/html/Elements/ShowTransactionAttachments
     --- a/share/html/Elements/ShowTransactionAttachments
     +++ b/share/html/Elements/ShowTransactionAttachments
     @@
      foreach my $message ( @{ $Attachments->{ $Parent || 0 } || [] } ) {
      
    -     if (RT->Config->Get('GnuPG')->{'Enable'}) {
    +     if (RT->Config->Get('Crypt')->{'Enable'}) {
     -        $m->comp( 'ShowGnuPGStatus', Attachment => $message, WarnUnsigned => $WarnUnsigned );
     +        $m->comp( 'ShowCryptStatus', Attachment => $message, WarnUnsigned => $WarnUnsigned );
          }
      
          $m->comp( 'ShowMessageHeaders',
     
    +diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
    +--- a/share/html/Elements/Tabs
    ++++ b/share/html/Elements/Tabs
    +@@
    +                     path  => '/Admin/Users/DashboardsInMenu.html?id=' . $id,
    +                 );
    +                 if ( RT->Config->Get('Crypt')->{'Enable'} ) {
    +-                    $tabs->child( keys    => title => loc('Private keys'),   path => "/Admin/Users/GnuPG.html?id=" . $id );
    ++                    $tabs->child( keys    => title => loc('Private keys'),   path => "/Admin/Users/Keys.html?id=" . $id );
    +                 }
    +                 $tabs->child( 'summary'   => title => loc('User Summary'),   path => "/User/Summary.html?id=" . $id );
    +             }
    +
     diff --git a/share/html/Prefs/Other.html b/share/html/Prefs/Other.html
     --- a/share/html/Prefs/Other.html
     +++ b/share/html/Prefs/Other.html
     @@
      
    - % if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
    + % if ( RT->Config->Get('Crypt')->{'Enable'} ) {
      <&|/Widgets/TitleBox, title => loc( 'Cryptography' ) &>
     -<&|/l&>Preferred key</&>: <& /Elements/GnuPG/SelectKeyForEncryption, EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey &>
     +<&|/l&>Preferred key</&>: <& /Elements/Crypt/SelectKeyForEncryption, EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey &>
    @@ -1438,7 +1730,7 @@
     +
     +my $attachments = $txn->Attachments;
     +while ( my $attachment = $attachments->Next ) {
    -+    next unless $attachment->ContentType =~ m{^x-application-rt/gpg-encrypted\b};
    ++    next unless $attachment->ContentType =~ m{^x-application-rt/[^-]+-encrypted\b};
     +    $encrypted = 1;
     +    last;
     +}
    @@ -1544,7 +1836,7 @@
     -
     -my $attachments = $txn->Attachments;
     -while ( my $attachment = $attachments->Next ) {
    --    next unless $attachment->ContentType =~ m{^x-application-rt/gpg-encrypted\b};
    +-    next unless $attachment->ContentType =~ m{^x-application-rt/[^-]+-encrypted\b};
     -    $encrypted = 1;
     -    last;
     -}
147:  ff6ec4b < ---:  ------- return more of master's RT::Test
148:  89cf615 < ---:  ------- we do the same in bootstrap_logging
149:  59808f4 < ---:  ------- separate smime keyring dir from gpg's
150:  1a9914a < ---:  ------- AllowEncryptDataInDB is in %Crypt now, not %GnuPG
151:  d128eb2 < ---:  ------- Switch to using the FormatStatus method
152:  e611e98 < ---:  ------- Show status if you're only using S/MIME
153:  ce9b99a < ---:  ------- Switch the label because there can be S/MIME messages
155:  ea52566 < ---:  ------- more protection, make sure crypt modules are laoded
156:  ec225ff < ---:  ------- drop shift() call, shouldn't be there
157:  5ef4c37 < ---:  ------- be double sure openssl path is set to something
158:  1b9e57b < ---:  ------- special case of how openssl prints SMIME certs
159:  3278e04 < ---:  ------- special case of how openssl prints SMIME certs
160:  0a70506 < ---:  ------- newlines don't survive in header fields
163:  956f749 < ---:  ------- format status string so multiple can be appended together
164:  4f41cea < ---:  ------- fix detecting of bad recipients during SMIME encryption
165:  e64d80e < ---:  ------- test how we parse certificate information
166:  69c555f ! 108:  da8523b Reword UI messages implying the GnuPG is the only form of encryption
    @@ -1,6 +1,36 @@
    -Author: Ruslan Zakirov <ruz at bestpractical.com>
    +Author: Alex Vandiver <alexmv at bestpractical.com>
     
    -    adjust wording in KeyIssues to suite GnuPG and SMIME
    +    Reword UI messages implying the GnuPG is the only form of encryption
    +
    +diff --git a/share/html/Admin/Queues/Modify.html b/share/html/Admin/Queues/Modify.html
    +--- a/share/html/Admin/Queues/Modify.html
    ++++ b/share/html/Admin/Queues/Modify.html
    +@@
    + % if ( my $email = $QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress') ) {
    + <& /Admin/Elements/ShowKeyInfo, Type => 'private', EmailAddress => $email &>
    + % } else {
    +-<&|/Widgets/TitleBox, title => loc( 'GnuPG private keys') &>
    +-<i><&|/l&>You have enabled GnuPG support but have not set a correspondence address for this queue.</&>
    +-<&|/l&>You must set a correspondence address for this queue in order to configure a GnuPG private key.</&></i>
    ++<&|/Widgets/TitleBox, title => loc( 'Private keys') &>
    ++<i><&|/l&>You have enabled encryption support but have not set a correspondence address for this queue.</&>
    ++<&|/l&>You must set a correspondence address for this queue in order to configure a private key.</&></i>
    + </&>
    + % }
    + </td></tr>
    +@@
    + % if ( my $email = $QueueObj->CommentAddress || RT->Config->Get('CommentAddress') ) {
    + <& /Admin/Elements/ShowKeyInfo, Type => 'private', EmailAddress => $email &>
    + % } else {
    +-<&|/Widgets/TitleBox, title => loc( 'GnuPG private keys') &>
    +-<i><&|/l&>You have enabled GnuPG support but have not set a comment address for this queue.</&>
    +-<&|/l&>You must set a comment address for this queue in order to configure a GnuPG private key.</&></i>
    ++<&|/Widgets/TitleBox, title => loc( 'Private keys') &>
    ++<i><&|/l&>You have enabled encryption support but have not set a comment address for this queue.</&>
    ++<&|/l&>You must set a comment address for this queue in order to configure a private key.</&></i>
    + </&>
    + %}
    + </td></tr>
     
     diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
     --- a/share/html/Elements/Crypt/KeyIssues
    @@ -41,3 +71,29 @@
          Name         => 'UseKey-'. $issue->{'EmailAddress'},
          EmailAddress => $issue->{'EmailAddress'},
     
    +diff --git a/share/html/Elements/ShowCryptStatus b/share/html/Elements/ShowCryptStatus
    +--- a/share/html/Elements/ShowCryptStatus
    ++++ b/share/html/Elements/ShowCryptStatus
    +@@
    + %#
    + %# END BPS TAGGED BLOCK }}}
    + <table class="crypt-runs">
    +-<tr><td align="right" class="labeltop" rowspan="<% scalar @messages %>">GnuPG:</td>
    ++<tr><td align="right" class="labeltop" rowspan="<% scalar @messages %>">Encryption:</td>
    + <td><% shift @messages %></td></tr>
    + 
    + % foreach my $msg( @messages ) {
    +
    +diff --git a/share/html/Elements/ShowTransaction b/share/html/Elements/ShowTransaction
    +--- a/share/html/Elements/ShowTransaction
    ++++ b/share/html/Elements/ShowTransaction
    +@@
    +             && RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'}
    +         ) {
    +             push @actions, {
    +-                class  => "gpg-link",
    ++                class  => "encryption-link",
    +                 title  => loc('Encrypt/Decrypt'),
    +                 path   => $EncryptionPath
    +                     .'?id='. $Transaction->id
    +
---:  ------- > 109:  d79a53c Display Created and Expire dates in the user's preferred format by setting CurrentUser
---:  ------- > 110:  bc4b1b1 On UIDs with neither expiration nor created dates (SMIME), skip the dates
167:  925e346 ! 111:  ff42449 Display GnuPG/SMIME issues box in yellow, much like results
    @@ -1,6 +1,6 @@
     Author: Ruslan Zakirov <ruz at bestpractical.com>
     
    -    show GnuPG/SMIME issues box in yellow like results
    +    Display GnuPG/SMIME issues box in yellow, much like results
     
     diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
     --- a/share/html/Elements/Crypt/KeyIssues
169:  8f3de9d < ---:  ------- test that notification to RT owner has correct info
170:  a99b5b1 < ---:  ------- Adapt merged 4.2/gnupg-refactor for changes on the branch
171:  64b0f76 < ---:  ------- Fix a typo, preventing emails from setting internal encryption header
172:  bc87c56 < ---:  ------- Remove internal signing and encryption hints from incoming mail
173:  178a31c < ---:  ------- Refactor shared code controlling if a message will be encrypted or signed (cherry picked from commit 8f242072bb196e155ec9a5fbbf06bc8a2d3c2813)
174:  35d74e1 < ---:  ------- The "Always Sign, even redistributed email" option is now called SignAuto
175:  6c2ef43 < ---:  ------- Stop looking for things in the GnuPG configuration
176:  62adec8 < ---:  ------- Let MIME::Head modify the crypt status headers to handle continuations
177:  9c479ba < ---:  ------- make SMIME more verbose on how it looks for keys
178:  f20eced < ---:  ------- test fixup, handle warning in t/crypt/no-signer-address.t
179:  a958339 < ---:  ------- test fixup, handle expected warnings
180:  6c53063 < ---:  ------- test fixup, handle expected warning
181:  97898dc < ---:  ------- fixup, we changed privacy from PGP to GnuPG
182:  b6ad583 < ---:  ------- test expects SignAuto
183:  03f80d8 < ---:  ------- some expected warnings in the test
184:  6341237 < ---:  ------- fixup, misplaced code
185:  f8f8410 < ---:  ------- fixup, typo on conflict resolution
186:  e23e280 < ---:  ------- License tag new files
187:  fb0de06 < ---:  ------- Fix tests to pass updated policy
188:  a0951a3 < ---:  ------- GetKeyContent should return a scalar value
189:  bc35471 < ---:  ------- Don't generate warnings if there is no 'SMIME Key' CF
190:  ae49844 < ---:  ------- Adjust test for new title as of 3ed0f64
191:  69f2731 < ---:  ------- Adjust security signing test for new GetKeysInfo API
192:  e702e73 < ---:  ------- The top-level $msg is a multipart/alternative; use $txn->content for the body
---:  ------- > 112:  5750602 Resolve SMIME/GnuPG inconsistency when asking for non-existent keys
---:  ------- > 113:  ead85be Visualize trust level of signing entity



More information about the Rt-commit mailing list