This commit was generated by cvs2svn to compensate for changes in r12472,
[freeside.git] / rt / lib / RT / Crypt / GnuPG.pm
index 5581df1..bb8b2db 100644 (file)
@@ -1,40 +1,40 @@
 # BEGIN BPS TAGGED BLOCK {{{
-# 
+#
 # COPYRIGHT:
-# 
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-#                                          <jesse@bestpractical.com>
-# 
+#
+# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+#                                          <sales@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
@@ -43,7 +43,7 @@
 # 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 }}}
 
 use strict;
@@ -351,6 +351,8 @@ my %supported_opt = map { $_ => 1 } qw(
        verbose
 );
 
+our $RE_FILE_EXTENSIONS = qr/pgp|asc/i;
+
 # DEV WARNING: always pass all STD* handles to GnuPG interface even if we don't
 # need them, just pass 'new IO::Handle' and then close it after safe_run_child.
 # we don't want to leak anything into FCGI/Apache/MP handles, this break things.
@@ -891,6 +893,8 @@ sub FindProtectedParts {
 
     # inline PGP block, only in singlepart
     unless ( $entity->is_multipart ) {
+        my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
+
         my $io = $entity->open('r');
         unless ( $io ) {
             $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
@@ -902,8 +906,8 @@ sub FindProtectedParts {
             $RT::Logger->debug("Found $type inline part");
             return {
                 Type    => $type,
-                Format  => 'Inline',
-                Data  => $entity,
+                Format  => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
+                Data    => $entity,
             };
         }
         $io->close;
@@ -1000,7 +1004,7 @@ sub FindProtectedParts {
 
     # attachments with inline encryption
     my @encrypted_indices =
-        grep {($entity->parts($_)->head->recommended_filename || '') =~ /\.pgp$/}
+        grep {($entity->parts($_)->head->recommended_filename || '') =~ /\.${RE_FILE_EXTENSIONS}$/}
             0 .. $entity->parts - 1;
 
     foreach my $i ( @encrypted_indices ) {
@@ -1026,46 +1030,62 @@ sub FindProtectedParts {
 =cut
 
 sub VerifyDecrypt {
-    my %args = ( Entity => undef, Detach => 1, SetStatus => 1, @_ );
+    my %args = (
+        Entity    => undef,
+        Detach    => 1,
+        SetStatus => 1,
+        AddStatus => 0,
+        @_
+    );
     my @protected = FindProtectedParts( Entity => $args{'Entity'} );
     my @res;
     # XXX: detaching may brake nested signatures
     foreach my $item( grep $_->{'Type'} eq 'signed', @protected ) {
+        my $status_on;
         if ( $item->{'Format'} eq 'RFC3156' ) {
             push @res, { VerifyRFC3156( %$item, SetStatus => $args{'SetStatus'} ) };
             if ( $args{'Detach'} ) {
                 $item->{'Top'}->parts( [ $item->{'Data'} ] );
                 $item->{'Top'}->make_singlepart;
             }
-            $item->{'Top'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
-                if $args{'SetStatus'};
+            $status_on = $item->{'Top'};
         } elsif ( $item->{'Format'} eq 'Inline' ) {
             push @res, { VerifyInline( %$item ) };
-            $item->{'Data'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
-                if $args{'SetStatus'};
+            $status_on = $item->{'Data'};
         } elsif ( $item->{'Format'} eq 'Attachment' ) {
             push @res, { VerifyAttachment( %$item ) };
             if ( $args{'Detach'} ) {
-                $item->{'Top'}->parts( [ grep "$_" ne $item->{'Signature'}, $item->{'Top'}->parts ] );
+                $item->{'Top'}->parts( [
+                    grep "$_" ne $item->{'Signature'}, $item->{'Top'}->parts
+                ] );
                 $item->{'Top'}->make_singlepart;
             }
-            $item->{'Data'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
-                if $args{'SetStatus'};
+            $status_on = $item->{'Data'};
+        }
+        if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
+            my $method = $args{'AddStatus'} ? 'add' : 'set';
+            $status_on->head->$method(
+                'X-RT-GnuPG-Status' => $res[-1]->{'status'}
+            );
         }
     }
     foreach my $item( grep $_->{'Type'} eq 'encrypted', @protected ) {
+        my $status_on;
         if ( $item->{'Format'} eq 'RFC3156' ) {
             push @res, { DecryptRFC3156( %$item ) };
-            $item->{'Top'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
-                if $args{'SetStatus'};
+            $status_on = $item->{'Top'};
         } elsif ( $item->{'Format'} eq 'Inline' ) {
             push @res, { DecryptInline( %$item ) };
-            $item->{'Data'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
-                if $args{'SetStatus'};
+            $status_on = $item->{'Data'};
         } elsif ( $item->{'Format'} eq 'Attachment' ) {
             push @res, { DecryptAttachment( %$item ) };
-            $item->{'Data'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
-                if $args{'SetStatus'};
+            $status_on = $item->{'Data'};
+        }
+        if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
+            my $method = $args{'AddStatus'} ? 'add' : 'set';
+            $status_on->head->$method(
+                'X-RT-GnuPG-Status' => $res[-1]->{'status'}
+            );
         }
     }
     return @res;
@@ -1290,11 +1310,12 @@ sub DecryptInline {
         die "Entity has no body, never should happen";
     }
 
+    my %res;
+
     my ($had_literal, $in_block) = ('', 0);
     my ($block_fh, $block_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $block_fh, ':raw';
 
-    my %res;
     while ( defined(my $str = $io->getline) ) {
         if ( $in_block && $str =~ /^-----END PGP (?:MESSAGE|SIGNATURE)-----/ ) {
             print $block_fh $str;
@@ -1335,6 +1356,26 @@ sub DecryptInline {
     }
     $io->close;
 
+    if ( $in_block ) {
+        # we're still in a block, this not bad not good. let's try to
+        # decrypt what we have, it can be just missing -----END PGP...
+        seek $block_fh, 0, 0;
+
+        my ($res_fh, $res_fn);
+        ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
+            %args,
+            GnuPG => $gnupg,
+            BlockHandle => $block_fh,
+        );
+        return %res unless $res_fh;
+
+        print $tmp_fh "-----BEGIN OF PGP PROTECTED PART-----\n" if $had_literal;
+        while (my $buf = <$res_fh> ) {
+            print $tmp_fh $buf;
+        }
+        print $tmp_fh "-----END OF PART-----\n" if $had_literal;
+    }
+
     seek $tmp_fh, 0, 0;
     $args{'Data'}->bodyhandle( new MIME::Body::File $tmp_fn );
     $args{'Data'}->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh;
@@ -1435,9 +1476,16 @@ sub DecryptAttachment {
     $args{'Data'}->bodyhandle( new MIME::Body::File $res_fn );
     $args{'Data'}->{'__store_tmp_handle_to_avoid_early_cleanup'} = $res_fh;
 
-    my $filename = $args{'Data'}->head->recommended_filename;
-    $filename =~ s/\.pgp$//i;
-    $args{'Data'}->head->mime_attr( $_ => $filename )
+    my $head = $args{'Data'}->head;
+
+    # we can not trust original content type
+    # TODO: and don't have way to detect, so we just use octet-stream
+    # some clients may send .asc files (encryped) as text/plain
+    $head->mime_attr( "Content-Type" => 'application/octet-stream' );
+
+    my $filename = $head->recommended_filename;
+    $filename =~ s/\.${RE_FILE_EXTENSIONS}$//i;
+    $head->mime_attr( $_ => $filename )
         foreach (qw(Content-Type.name Content-Disposition.filename));
 
     return %res;
@@ -2399,11 +2447,17 @@ sub Probe {
 # it's general error system error or incorrect command, command is correct,
 # but there is no way to get actuall error
     if ( $? && ($? >> 8) != 2 ) {
-        $RT::Logger->debug(
-            "Probe for GPG failed."
+        my $msg = "Probe for GPG failed."
             ." Process exitted with code ". ($? >> 8)
             . ($? & 127 ? (" as recieved signal ". ($? & 127)) : '')
-        );
+            . ".";
+        foreach ( qw(stderr logger status) ) {
+            my $tmp = do { local $/; readline $handle{$_} };
+            next unless $tmp && $tmp =~ /\S/s;
+            close $handle{$_};
+            $msg .= "\n$_:\n$tmp\n";
+        }
+        $RT::Logger->debug( $msg );
         return 0;
     }
     return 1;
@@ -2411,30 +2465,16 @@ sub Probe {
 
 
 sub _make_gpg_handles {
-    my %handle_map = (
-        stdin  => IO::Handle->new(),
-        stdout => IO::Handle->new(),
-        stderr => IO::Handle->new(),
-        logger => IO::Handle->new(),
-        status => IO::Handle->new(),
-        command => IO::Handle->new(),
-
-
-            @_);
+    my %handle_map = (@_);
+    $handle_map{$_} = IO::Handle->new
+        foreach grep !defined $handle_map{$_}, 
+        qw(stdin stdout stderr logger status command);
 
     my $handles = GnuPG::Handles->new(%handle_map);
     return ($handles, \%handle_map);
 }
 
-eval "require RT::Crypt::GnuPG_Vendor";
-if ($@ && $@ !~ qr{^Can't locate RT/Crypt/GnuPG_Vendor.pm}) {
-    die $@;
-};
-
-eval "require RT::Crypt::GnuPG_Local";
-if ($@ && $@ !~ qr{^Can't locate RT/Crypt/GnuPG_Local.pm}) {
-    die $@;
-};
+RT::Base->_ImportOverlays();
 
 # helper package to avoid using temp file
 package IO::Handle::CRLF;