[Rt-commit] rt branch, 4.2/gnupg-refactor, created. rt-4.0.4-245-gcd978dd

Kevin Falcone falcone at bestpractical.com
Fri Sep 28 15:23:08 EDT 2012


The branch, 4.2/gnupg-refactor has been created
        at  cd978dd12587eddf319d0cc53f50650f687b4145 (commit)

- Log -----------------------------------------------------------------
commit 47a9cb5ba7de2ac50a0b94a89c85cf00c2864408
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Aug 31 14:31:52 2012 -0400

    Refactor code which calls GPG::Interface to elimate duplicate code
    
    Many of the functions in RT::Crypt::GnuPG contain nearly-duplicate
    swaths of code.  Refactor these into a common CallGnuPG method, which
    deals with setting up IO handles, printing content, trapping errors,
    reading output, and returning results in the form of a data structure.

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index d252504..1b08649 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -364,6 +364,107 @@ our $RE_FILE_EXTENSIONS = qr/pgp|asc/i;
 #            ...
 #        );
 
+=head2 CallGnuPG
+
+=cut
+
+sub CallGnuPG {
+    my %args = (
+        Options     => undef,
+        Key         => undef,
+        Recipients  => [],
+        Passphrase  => undef,
+
+        Method      => undef,
+        CommandArgs => [],
+
+        Content     => undef,
+        Handles     => {},
+        Direct      => undef,
+        Output      => undef,
+        @_
+    );
+
+    my %handle = %{$args{Handles}};
+    my ($handles, $handle_list) = _make_gpg_handles( %handle );
+    $handles->options( $_ )->{'direct'} = 1
+        for @{$args{Direct} || [keys %handle] };
+    %handle = %$handle_list;
+
+    my $content = $args{Content};
+    my $method  = $args{Method};
+
+    my %GnuPGOptions = RT->Config->Get('GnuPGOptions');
+    my %opt = (
+        'digest-algo' => 'SHA1',
+        %GnuPGOptions,
+        %{ $args{Options} || {} },
+    );
+    my $gnupg = GnuPG::Interface->new;
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+    );
+    $gnupg->options->armor( 1 );
+    $gnupg->options->meta_interactive( 0 );
+    $gnupg->options->default_key( $args{Key} );
+
+    my %seen;
+    $gnupg->options->push_recipients( $_ ) for
+        map { UseKeyForEncryption($_) || $_ }
+        grep { !$seen{ $_ }++ }
+            @{ $args{Recipients} || [] };
+
+    $args{Passphrase} = $GnuPGOptions{passphrase}
+        unless defined $args{'Passphrase'};
+    $args{Passphrase} = GetPassphrase( Address => $args{Key} )
+        unless defined $args{'Passphrase'};
+    $gnupg->passphrase( $args{'Passphrase'} );
+
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $pid = safe_run_child {
+            $gnupg->$method(
+                handles      => $handles,
+                command_args => $args{CommandArgs},
+            )
+        };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            if (Scalar::Util::blessed($content) and $content->can("print")) {
+                $content->print( $handle{'stdin'} );
+            } elsif (ref($content) eq "SCALAR") {
+                $handle{'stdin'}->print( ${ $content } );
+            } elsif (defined $content) {
+                $handle{'stdin'}->print( $content );
+            }
+            close $handle{'stdin'} or die "Can't close gnupg handle: $!";
+        }
+        waitpid $pid, 0;
+    };
+    my $err = $@;
+    if ($args{Output}) {
+        push @{$args{Output}}, readline $handle{stdout};
+        close $handle{stdout};
+    }
+
+    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 exited with error code ". ($res{'exit_code'} >> 8);
+    }
+
+    return %res;
+}
+
 =head2 SignEncrypt Entity => MIME::Entity, [ Encrypt => 1, Sign => 1, ... ]
 
 Signs and/or encrypts an email message with GnuPG utility.
@@ -435,28 +536,7 @@ sub SignEncryptRFC3156 {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnuPGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined $args{'Passphrase'};
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'default_key'} = $args{'Signer'}
-        if $args{'Sign'} && $args{'Signer'};
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
-    );
-
     my $entity = $args{'Entity'};
-
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
-    }
-
     my %res;
     if ( $args{'Sign'} && !$args{'Encrypt'} ) {
         # required by RFC3156(Ch. 5) and RFC1847(Ch. 2.1)
@@ -468,46 +548,26 @@ sub SignEncryptRFC3156 {
                 );
             }
         }
-
-        my ($handles, $handle_list) = _make_gpg_handles(stdin =>IO::Handle::CRLF->new );
-        my %handle = %$handle_list;
-
-        $gnupg->passphrase( $args{'Passphrase'} );
-
-        eval {
-            local $SIG{'CHLD'} = 'DEFAULT';
-            my $pid = safe_run_child { $gnupg->detach_sign( handles => $handles ) };
-            $entity->make_multipart( 'mixed', Force => 1 );
-            {
-                local $SIG{'PIPE'} = 'IGNORE';
-                $entity->parts(0)->print( $handle{'stdin'} );
-                close $handle{'stdin'};
-            }
-            waitpid $pid, 0;
-        };
-        my $err = $@;
-        my @signature = readline $handle{'stdout'};
-        close $handle{'stdout'};
-
-        $res{'exit_code'} = $?;
-        foreach ( qw(stderr logger status) ) {
-            $res{$_} = do { local $/; 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;
-        }
+        $entity->make_multipart( 'mixed', Force => 1 );
+
+        my @signature;
+        %res = CallGnuPG(
+            Key        => $args{'Signer'},
+            Method     => "detach_sign",
+            Handles    => { stdin => IO::Handle::CRLF->new },
+            Direct     => [],
+            Passphrase => $args{'Passphrase'},
+            Content    => $entity->parts(0),
+            Output     => \@signature,
+        );
+        return %res if $res{message};
 
         # setup RFC1847(Ch.2.1) requirements
         my $protocol = 'application/pgp-signature';
+        my $algo = RT->Config->Get('GnuPGOptions')->{'digest-algo'} || 'SHA1';
         $entity->head->mime_attr( 'Content-Type' => 'multipart/signed' );
         $entity->head->mime_attr( 'Content-Type.protocol' => $protocol );
-        $entity->head->mime_attr( 'Content-Type.micalg'   => 'pgp-'. lc $opt{'digest-algo'} );
+        $entity->head->mime_attr( 'Content-Type.micalg'   => 'pgp-'. lc $algo );
         $entity->attach(
             Type        => $protocol,
             Disposition => 'inline',
@@ -516,48 +576,23 @@ sub SignEncryptRFC3156 {
         );
     }
     if ( $args{'Encrypt'} ) {
-        my %seen;
-        $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
-            grep !$seen{ $_ }++, map $_->address,
+        my @recipients = map $_->address,
             map Email::Address->parse( $entity->head->get( $_ ) ),
             qw(To Cc Bcc);
 
         my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
         binmode $tmp_fh, ':raw';
 
-        my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-        my %handle = %$handle_list;
-        $handles->options( 'stdout'  )->{'direct'} = 1;
-        $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
-
-        eval {
-            local $SIG{'CHLD'} = 'DEFAULT';
-            my $pid = safe_run_child { $args{'Sign'}
-                ? $gnupg->sign_and_encrypt( handles => $handles )
-                : $gnupg->encrypt( handles => $handles ) };
-            $entity->make_multipart( 'mixed', Force => 1 );
-            {
-                local $SIG{'PIPE'} = 'IGNORE';
-                $entity->parts(0)->print( $handle{'stdin'} );
-                close $handle{'stdin'};
-            }
-            waitpid $pid, 0;
-        };
-
-        $res{'exit_code'} = $?;
-        foreach ( qw(stderr logger status) ) {
-            $res{$_} = do { local $/; 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 ( $@ || $? ) {
-            $res{'message'} = $@? $@: "gpg exited with error code ". ($? >> 8);
-            return %res;
-        }
+        $entity->make_multipart( 'mixed', Force => 1 );
+        %res = CallGnuPG(
+            Key        => $args{'Signer'},
+            Recipients => \@recipients,
+            Method     => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
+            Handles    => { stdout => $tmp_fh },
+            Passphrase => $args{'Passphrase'},
+            Content    => $entity->parts(0),
+        );
+        return %res if $res{message};
 
         my $protocol = 'application/pgp-encrypted';
         $entity->parts([]);
@@ -617,72 +652,23 @@ sub _SignEncryptTextInline {
     );
     return unless $args{'Sign'} || $args{'Encrypt'};
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'default_key'} = $args{'Signer'}
-        if $args{'Sign'} && $args{'Signer'};
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
-    );
-
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
-    }
-
-    if ( $args{'Encrypt'} ) {
-        $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
-            @{ $args{'Recipients'} || [] };
-    }
-
-    my %res;
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-    my %handle = %$handle_list;
-
-    $handles->options( 'stdout'  )->{'direct'} = 1;
-    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
-
     my $entity = $args{'Entity'};
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $method = $args{'Sign'} && $args{'Encrypt'}
-            ? 'sign_and_encrypt'
-            : ($args{'Sign'}? 'clearsign': 'encrypt');
-        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $entity->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    my $err = $@;
-
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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;
-    }
+    my %res = CallGnuPG(
+        Key        => $args{'Signer'},
+        Recipients => $args{'Recipients'},
+        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+                      ? 'sign_and_encrypt'
+                      : ( $args{'Sign'}
+                        ? 'clearsign'
+                        : 'encrypt' ) ),
+        Handles    => { stdout => $tmp_fh },
+        Passphrase => $args{'Passphrase'},
+        Content    => $entity->bodyhandle,
+    );
+    return %res if $res{message};
 
     $entity->bodyhandle( MIME::Body::File->new( $tmp_fn) );
     $entity->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh;
@@ -705,71 +691,25 @@ sub _SignEncryptAttachmentInline {
     );
     return unless $args{'Sign'} || $args{'Encrypt'};
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'default_key'} = $args{'Signer'}
-        if $args{'Sign'} && $args{'Signer'};
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
-    );
-
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
-    }
 
     my $entity = $args{'Entity'};
-    if ( $args{'Encrypt'} ) {
-        $gnupg->options->push_recipients( $_ ) foreach
-            map UseKeyForEncryption($_) || $_,
-            @{ $args{'Recipients'} || [] };
-    }
-
-    my %res;
 
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout'  )->{'direct'} = 1;
-    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $method = $args{'Sign'} && $args{'Encrypt'}
-            ? 'sign_and_encrypt'
-            : ($args{'Sign'}? 'detach_sign': 'encrypt');
-        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $entity->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    my $err = $@;
-
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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;
-    }
+    my %res = CallGnuPG(
+        Key        => $args{'Signer'},
+        Recipients => $args{'Recipients'},
+        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+                      ? 'sign_and_encrypt'
+                      : ( $args{'Sign'}
+                        ? 'detach_sign'
+                        : 'encrypt' ) ),
+        Handles    => { stdout => $tmp_fh },
+        Passphrase => $args{'Passphrase'},
+        Content    => $entity->bodyhandle,
+    );
+    return %res if $res{message};
 
     my $filename = mime_recommended_filename( $entity ) || 'no_name';
     if ( $args{'Sign'} && !$args{'Encrypt'} ) {
@@ -807,70 +747,22 @@ sub SignEncryptContent {
     );
     return unless $args{'Sign'} || $args{'Encrypt'};
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'default_key'} = $args{'Signer'}
-        if $args{'Sign'} && $args{'Signer'};
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
-    );
-
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
-    }
-
-    if ( $args{'Encrypt'} ) {
-        $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
-            @{ $args{'Recipients'} || [] };
-    }
-
-    my %res;
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout'  )->{'direct'} = 1;
-    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $method = $args{'Sign'} && $args{'Encrypt'}
-            ? 'sign_and_encrypt'
-            : ($args{'Sign'}? 'clearsign': 'encrypt');
-        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $handle{'stdin'}->print( ${ $args{'Content'} } );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    my $err = $@;
-
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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;
-    }
+    my %res = CallGnuPG(
+        Key        => $args{'Signer'},
+        Recipients => $args{'Recipients'},
+        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+                      ? 'sign_and_encrypt'
+                      : ( $args{'Sign'}
+                        ? 'clearsign'
+                        : 'encrypt' ) ),
+        Handles    => { stdout => $tmp_fh },
+        Passphrase => $args{'Passphrase'},
+        Content    => $args{'Content'},
+    );
+    return %res if $res{message};
 
     ${ $args{'Content'} } = '';
     seek $tmp_fh, 0, 0;
@@ -1096,14 +988,6 @@ sub VerifyInline { return DecryptInline( @_ ) }
 sub VerifyAttachment {
     my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     foreach ( $args{'Data'}, $args{'Signature'} ) {
         next unless $_->bodyhandle->is_encoded;
 
@@ -1116,82 +1000,28 @@ sub VerifyAttachment {
     $args{'Data'}->bodyhandle->print( $tmp_fh );
     $tmp_fh->flush;
 
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->verify(
-            handles => $handles, command_args => [ '-', $tmp_fn ]
-        ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $args{'Signature'}->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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 ( $@ || $? ) {
-        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-    }
-    return %res;
+    return CallGnuPG(
+        Method      => "verify",
+        CommandArgs => [ '-', $tmp_fn ],
+        Passphrase  => $args{'Passphrase'},
+        Content     => $args{'Signature'}->bodyhandle,
+    );
 }
 
 sub VerifyRFC3156 {
     my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw:eol(CRLF?)';
     $args{'Data'}->print( $tmp_fh );
     $tmp_fh->flush;
 
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->verify(
-            handles => $handles, command_args => [ '-', $tmp_fn ]
-        ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $args{'Signature'}->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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 ( $@ || $? ) {
-        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-    }
-    return %res;
+    return CallGnuPG(
+        Method      => "verify",
+        CommandArgs => [ '-', $tmp_fn ],
+        Passphrase  => $args{'Passphrase'},
+        Content     => $args{'Signature'}->bodyhandle,
+    );
 }
 
 sub DecryptRFC3156 {
@@ -1203,66 +1033,27 @@ sub DecryptRFC3156 {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     if ( $args{'Data'}->bodyhandle->is_encoded ) {
         require RT::EmailParser;
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
-        unless defined $args{'Passphrase'};
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout' )->{'direct'} = 1;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        $gnupg->passphrase( $args{'Passphrase'} );
-        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $args{'Data'}->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'}
-        }
-
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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'} && $?;
+    my %res = CallGnuPG(
+        Method      => "decrypt",
+        Handles     => { stdout => $tmp_fh },
+        Passphrase  => $args{'Passphrase'},
+        Content     => $args{'Data'}->bodyhandle,
+    );
 
     # if the decryption is fine but the signature is bad, then without this
     # status check we lose the decrypted text
     # XXX: add argument to the function to control this check
-    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
-        if ( $@ || $? ) {
-            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-            return %res;
-        }
-    }
+    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
+
+    return %res if $res{message};
 
     seek $tmp_fh, 0, 0;
     my $parser = RT::EmailParser->new();
@@ -1281,27 +1072,11 @@ sub DecryptInline {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnuPGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     if ( $args{'Data'}->bodyhandle->is_encoded ) {
         require RT::EmailParser;
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
-        unless defined $args{'Passphrase'};
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
@@ -1327,7 +1102,6 @@ sub DecryptInline {
             my ($res_fh, $res_fn);
             ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
                 %args,
-                GnuPG => $gnupg,
                 BlockHandle => $block_fh,
             );
             return %res unless $res_fh;
@@ -1364,7 +1138,6 @@ sub DecryptInline {
         my ($res_fh, $res_fn);
         ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
             %args,
-            GnuPG => $gnupg,
             BlockHandle => $block_fh,
         );
         return %res unless $res_fh;
@@ -1384,49 +1157,26 @@ sub DecryptInline {
 
 sub _DecryptInlineBlock {
     my %args = (
-        GnuPG => undef,
         BlockHandle => undef,
         Passphrase => undef,
         @_
     );
-    my $gnupg = $args{'GnuPG'};
 
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(
-            stdin => $args{'BlockHandle'}, 
-            stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout' )->{'direct'} = 1;
-    $handles->options( 'stdin' )->{'direct'} = 1;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        $gnupg->passphrase( $args{'Passphrase'} );
-        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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'} && $?;
+    my %res = CallGnuPG(
+        Method      => "decrypt",
+        Handles     => { stdout => $tmp_fh, stdin => $args{'BlockHandle'} },
+        Passphrase  => $args{'Passphrase'},
+    );
 
     # if the decryption is fine but the signature is bad, then without this
     # status check we lose the decrypted text
     # XXX: add argument to the function to control this check
-    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
-        if ( $@ || $? ) {
-            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-            return (undef, undef, %res);
-        }
-    }
+    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
+
+    return (undef, undef, %res) if $res{message};
 
     seek $tmp_fh, 0, 0;
     return ($tmp_fh, $tmp_fn, %res);
@@ -1440,27 +1190,11 @@ sub DecryptAttachment {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnuPGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     if ( $args{'Data'}->bodyhandle->is_encoded ) {
         require RT::EmailParser;
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
-        unless defined $args{'Passphrase'};
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
     $args{'Data'}->bodyhandle->print( $tmp_fh );
@@ -1468,7 +1202,6 @@ sub DecryptAttachment {
 
     my ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
         %args,
-        GnuPG => $gnupg,
         BlockHandle => $tmp_fh,
     );
     return %res unless $res_fh;
@@ -1498,62 +1231,22 @@ sub DecryptContent {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
-    $args{'Passphrase'} = GetPassphrase()
-        unless defined $args{'Passphrase'};
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(
-            stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout' )->{'direct'} = 1;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        $gnupg->passphrase( $args{'Passphrase'} );
-        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            print { $handle{'stdin'} } ${ $args{'Content'} };
-            close $handle{'stdin'};
-        }
-
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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'} && $?;
+    my %res = CallGnuPG(
+        Method      => "decrypt",
+        Handles     => { stdout => $tmp_fh },
+        Passphrase  => $args{'Passphrase'},
+        Content     => $args{'Content'},
+    );
 
     # if the decryption is fine but the signature is bad, then without this
     # status check we lose the decrypted text
     # XXX: add argument to the function to control this check
-    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
-        if ( $@ || $? ) {
-            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-            return %res;
-        }
-    }
+    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
+
+    return %res if $res{'message'};
 
     ${ $args{'Content'} } = '';
     seek $tmp_fh, 0, 0;
@@ -2086,47 +1779,19 @@ sub GetKeysInfo {
         return (exit_code => 0) unless $force;
     }
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'with-colons'} = undef; # parseable format
-    $opt{'fingerprint'} = undef; # show fingerprint
-    $opt{'fixed-list-mode'} = undef; # don't merge uid with keys
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
+    my @info;
+    my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
+    my %res = CallGnuPG(
+        Options     => {
+            'with-colons'     => undef, # parseable format
+            'fingerprint'     => undef, # show fingerprint
+            'fixed-list-mode' => undef, # don't merge uid with keys
+        },
+        Method      => $method,
+        ( $email ? (CommandArgs => [$email]) : () ),
+        Output      => \@info,
     );
-
-    my %res;
-
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
-        my $pid = safe_run_child { $gnupg->$method( handles => $handles, $email? (command_args => $email) : () ) };
-        close $handle{'stdin'};
-        waitpid $pid, 0;
-    };
-
-    my @info = readline $handle{'stdout'};
-    close $handle{'stdout'};
-
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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 ( $@ || $? ) {
-        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-        return %res;
-    }
+    return %res if $res{'message'};
 
     @info = ParseKeysInfo( @info );
     $res{'info'} = \@info;
@@ -2316,7 +1981,7 @@ sub DeleteKey {
     my %res;
     $res{'exit_code'} = $?;
     foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
+        $res{$_} = do { local $/ = undef; readline $handle{$_} };
         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
         close $handle{$_};
     }
@@ -2332,43 +1997,10 @@ sub DeleteKey {
 sub ImportKey {
     my $key = shift;
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
+    return CallGnuPG(
+        Method      => "import_keys",
+        Content     => $key,
     );
-
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->wrap_call(
-            handles => $handles,
-            commands => ['--import'],
-        ) };
-        print { $handle{'stdin'} } $key;
-        close $handle{'stdin'};
-        waitpid $pid, 0;
-    };
-    my $err = $@;
-    close $handle{'stdout'};
-
-    my %res;
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; 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;
 }
 
 =head2 KEY
@@ -2425,7 +2057,7 @@ sub Probe {
     my ($handles, $handle_list) = _make_gpg_handles();
     my %handle = %$handle_list;
 
-    local $@;
+    local $@ = undef;
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';
         my $pid = safe_run_child { $gnupg->wrap_call( commands => ['--version' ], handles => $handles ) };
@@ -2449,7 +2081,7 @@ sub Probe {
             . ($? & 127 ? (" as recieved signal ". ($? & 127)) : '')
             . ".";
         foreach ( qw(stderr logger status) ) {
-            my $tmp = do { local $/; readline $handle{$_} };
+            my $tmp = do { local $/ = undef; readline $handle{$_} };
             next unless $tmp && $tmp =~ /\S/s;
             close $handle{$_};
             $msg .= "\n$_:\n$tmp\n";

commit dc46058f56a2d309429b62f0d65c8320007d5f2b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jul 26 16:29:47 2011 -0400

    Factor out code which finalizes GnuPG output into a data structure

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 1b08649..6698b57 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -447,6 +447,13 @@ sub CallGnuPG {
         close $handle{stdout};
     }
 
+    return Finalize( $err, \%handle );
+}
+
+sub Finalize {
+    my ($err, $handle) = @_;
+    my %handle = %{ $handle };
+
     my %res;
     $res{'exit_code'} = $?;
 
@@ -1978,20 +1985,7 @@ sub DeleteKey {
     my $err = $@;
     close $handle{'stdout'};
 
-    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;
+    return Finalize( $err, \%handle );
 }
 
 sub ImportKey {

commit 09ccedaaf224ea71a4659f9ff9f1760336149332
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Aug 31 14:32:48 2012 -0400

    Generalize CallGnuPG slightly more, allowing more code reuse
    
    Three remaining locations had not been altered to use CallGnuPG, for two
    reaspons: they required custom interactions with the streams beyond
    simply printing content into gnupg's stdin, and because GnuPG::Interface
    provided no direct method to call them.  Extend CallGnuPG to allow both
    of these features, which thus allows for more code removal.

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 6698b57..1f5e36c 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -423,10 +423,18 @@ sub CallGnuPG {
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';
         my $pid = safe_run_child {
-            $gnupg->$method(
-                handles      => $handles,
-                command_args => $args{CommandArgs},
-            )
+            if ($method =~ /^--/) {
+                $gnupg->wrap_call(
+                    handles      => $handles,
+                    commands     => [$method],
+                    command_args => $args{CommandArgs},
+                );
+            } else {
+                $gnupg->$method(
+                    handles      => $handles,
+                    command_args => $args{CommandArgs},
+                );
+            }
         };
         {
             local $SIG{'PIPE'} = 'IGNORE';
@@ -438,6 +446,7 @@ sub CallGnuPG {
                 $handle{'stdin'}->print( $content );
             }
             close $handle{'stdin'} or die "Can't close gnupg handle: $!";
+            $args{Callback}->(%handle) if $args{Callback};
         }
         waitpid $pid, 0;
     };
@@ -1957,35 +1966,18 @@ sub _ParseDate {
 sub DeleteKey {
     my $key = shift;
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->wrap_call(
-            handles => $handles,
-            commands => ['--delete-secret-and-public-key'],
-            command_args => [$key],
-        ) };
-        close $handle{'stdin'};
-        while ( my $str = readline $handle{'status'} ) {
-            if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL delete_key\..*/ ) {
-                print { $handle{'command'} } "y\n";
+    return CallGnuPG(
+        Method      => "--delete-secret-and-public-key",
+        CommandArgs => [$key],
+        Callback    => sub {
+            my %handle = @_;
+            while ( my $str = readline $handle{'status'} ) {
+                if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL delete_key\..*/ ) {
+                    print { $handle{'command'} } "y\n";
+                }
             }
-        }
-        waitpid $pid, 0;
-    };
-    my $err = $@;
-    close $handle{'stdout'};
-
-    return Finalize( $err, \%handle );
+        },
+    );
 }
 
 sub ImportKey {
diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 19dfb0d..05a3827 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1151,125 +1151,50 @@ sub lsign_gnupg_key {
     my $self = shift;
     my $key = shift;
 
-    require RT::Crypt::GnuPG; require GnuPG::Interface;
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $gnupg->options->hash_init(
-        RT::Crypt::GnuPG::_PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
-    my %handle; 
-    my $handles = GnuPG::Handles->new(
-        stdin   => ($handle{'input'}   = IO::Handle->new()),
-        stdout  => ($handle{'output'}  = IO::Handle->new()),
-        stderr  => ($handle{'error'}   = IO::Handle->new()),
-        logger  => ($handle{'logger'}  = IO::Handle->new()),
-        status  => ($handle{'status'}  = IO::Handle->new()),
-        command => ($handle{'command'} = IO::Handle->new()),
-    );
+    require RT::Crypt::GnuPG;
 
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        local @ENV{'LANG', 'LC_ALL'} = ('C', 'C');
-        my $pid = $gnupg->wrap_call(
-            handles => $handles,
-            commands => ['--lsign-key'],
-            command_args => [$key],
-        );
-        close $handle{'input'};
-        while ( my $str = readline $handle{'status'} ) {
-            if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL sign_uid\..*/ ) {
-                print { $handle{'command'} } "y\n";
+    return RT::Crypt::GnuPG::CallGnuPG(
+        Method      => '--lsign-key',
+        CommandArgs => [$key],
+        Callback    => sub {
+            my %handle = @_;
+            while ( my $str = readline $handle{'status'} ) {
+                if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL sign_uid\..*/ ) {
+                    print { $handle{'command'} } "y\n";
+                }
             }
-        }
-        waitpid $pid, 0;
-    };
-    my $err = $@;
-    close $handle{'output'};
-
-    my %res;
-    $res{'exit_code'} = $?;
-    foreach ( qw(error logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'error'} ) if $res{'error'};
-    $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;
+        },
+    );
 }
 
 sub trust_gnupg_key {
     my $self = shift;
     my $key = shift;
 
-    require RT::Crypt::GnuPG; require GnuPG::Interface;
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $gnupg->options->hash_init(
-        RT::Crypt::GnuPG::_PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
-    my %handle; 
-    my $handles = GnuPG::Handles->new(
-        stdin   => ($handle{'input'}   = IO::Handle->new()),
-        stdout  => ($handle{'output'}  = IO::Handle->new()),
-        stderr  => ($handle{'error'}   = IO::Handle->new()),
-        logger  => ($handle{'logger'}  = IO::Handle->new()),
-        status  => ($handle{'status'}  = IO::Handle->new()),
-        command => ($handle{'command'} = IO::Handle->new()),
-    );
+    require RT::Crypt::GnuPG;
 
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        local @ENV{'LANG', 'LC_ALL'} = ('C', 'C');
-        my $pid = $gnupg->wrap_call(
-            handles => $handles,
-            commands => ['--edit-key'],
-            command_args => [$key],
-        );
-        close $handle{'input'};
-
-        my $done = 0;
-        while ( my $str = readline $handle{'status'} ) {
-            if ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE keyedit.prompt/ ) {
-                if ( $done ) {
-                    print { $handle{'command'} } "quit\n";
-                } else {
-                    print { $handle{'command'} } "trust\n";
+    return RT::Crypt::GnuPG::CallGnuPG(
+        Method      => '--edit-key',
+        CommandArgs => [$key],
+        Callback    => sub {
+            my %handle = @_;
+            my $done = 0;
+            while ( my $str = readline $handle{'status'} ) {
+                if ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE keyedit.prompt/ ) {
+                    if ( $done ) {
+                        print { $handle{'command'} } "quit\n";
+                    } else {
+                        print { $handle{'command'} } "trust\n";
+                    }
+                } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE edit_ownertrust.value/ ) {
+                    print { $handle{'command'} } "5\n";
+                } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_BOOL edit_ownertrust.set_ultimate.okay/ ) {
+                    print { $handle{'command'} } "y\n";
+                    $done = 1;
                 }
-            } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE edit_ownertrust.value/ ) {
-                print { $handle{'command'} } "5\n";
-            } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_BOOL edit_ownertrust.set_ultimate.okay/ ) {
-                print { $handle{'command'} } "y\n";
-                $done = 1;
             }
-        }
-        waitpid $pid, 0;
-    };
-    my $err = $@;
-    close $handle{'output'};
-
-    my %res;
-    $res{'exit_code'} = $?;
-    foreach ( qw(error logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'error'} ) if $res{'error'};
-    $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;
+        },
+    );
 }
 
 sub started_ok {

commit 019089fdc3dcad6b39681c760d8befbe420837fb
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jul 26 18:06:15 2011 -0400

    Minor cleanups to Probe, the one remaining non-CallGnuPG gpg interaction

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 1f5e36c..289eac8 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2032,13 +2032,11 @@ properly (and false otherwise).
 
 
 sub Probe {
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
+    my $gnupg = GnuPG::Interface->new;
     $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
+        _PrepareGnuPGOptions( RT->Config->Get('GnuPGOptions') )
     );
+    $gnupg->options->meta_interactive( 0 );
 
     my ($handles, $handle_list) = _make_gpg_handles();
     my %handle = %$handle_list;
@@ -2046,7 +2044,12 @@ sub Probe {
     local $@ = undef;
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->wrap_call( commands => ['--version' ], handles => $handles ) };
+        my $pid = safe_run_child {
+            $gnupg->wrap_call(
+                commands => ['--version' ],
+                handles  => $handles
+            )
+        };
         close $handle{'stdin'};
         waitpid $pid, 0;
     };
@@ -2063,7 +2066,7 @@ sub Probe {
 # but there is no way to get actuall error
     if ( $? && ($? >> 8) != 2 ) {
         my $msg = "Probe for GPG failed."
-            ." Process exitted with code ". ($? >> 8)
+            ." Process exited with code ". ($? >> 8)
             . ($? & 127 ? (" as recieved signal ". ($? & 127)) : '')
             . ".";
         foreach ( qw(stderr logger status) ) {

commit f5504208fa0784d74f830e86e5476ce4099cdcd7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jul 26 16:30:25 2011 -0400

    Catch errors on close()

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 289eac8..ca3d8f0 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -445,7 +445,7 @@ sub CallGnuPG {
             } elsif (defined $content) {
                 $handle{'stdin'}->print( $content );
             }
-            close $handle{'stdin'} or die "Can't close gnupg handle: $!";
+            close $handle{'stdin'} or die "Can't close gnupg input handle: $!";
             $args{Callback}->(%handle) if $args{Callback};
         }
         waitpid $pid, 0;
@@ -453,7 +453,9 @@ sub CallGnuPG {
     my $err = $@;
     if ($args{Output}) {
         push @{$args{Output}}, readline $handle{stdout};
-        close $handle{stdout};
+        if (not close $handle{stdout}) {
+            $err ||= "Can't close gnupg output handle: $!";
+        }
     }
 
     return Finalize( $err, \%handle );
@@ -469,7 +471,9 @@ sub Finalize {
     foreach ( qw(stderr logger status) ) {
         $res{$_} = do { local $/ = undef; readline $handle{$_} };
         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
+        if (not close $handle{$_}) {
+            $err ||= "Can't close gnupg $_ handle: $!";
+        }
     }
     $RT::Logger->debug( $res{'status'} ) if $res{'status'};
     $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
@@ -2050,7 +2054,7 @@ sub Probe {
                 handles  => $handles
             )
         };
-        close $handle{'stdin'};
+        close $handle{'stdin'} or die "Can't close gnupg input handle: $!";
         waitpid $pid, 0;
     };
     if ( $@ ) {
@@ -2072,7 +2076,7 @@ sub Probe {
         foreach ( qw(stderr logger status) ) {
             my $tmp = do { local $/ = undef; readline $handle{$_} };
             next unless $tmp && $tmp =~ /\S/s;
-            close $handle{$_};
+            close $handle{$_} or $tmp .= "\nFailed to close: $!";
             $msg .= "\n$_:\n$tmp\n";
         }
         $RT::Logger->debug( $msg );

commit 4598f0b68ae536e57551da0a5b2d13676b67769b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jul 27 13:36:06 2011 -0400

    Rename Key to Signer for clarity and consistency

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index ca3d8f0..547f93d 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -371,7 +371,7 @@ our $RE_FILE_EXTENSIONS = qr/pgp|asc/i;
 sub CallGnuPG {
     my %args = (
         Options     => undef,
-        Key         => undef,
+        Signer      => undef,
         Recipients  => [],
         Passphrase  => undef,
 
@@ -406,7 +406,7 @@ sub CallGnuPG {
     );
     $gnupg->options->armor( 1 );
     $gnupg->options->meta_interactive( 0 );
-    $gnupg->options->default_key( $args{Key} );
+    $gnupg->options->default_key( $args{Signer} );
 
     my %seen;
     $gnupg->options->push_recipients( $_ ) for
@@ -416,7 +416,7 @@ sub CallGnuPG {
 
     $args{Passphrase} = $GnuPGOptions{passphrase}
         unless defined $args{'Passphrase'};
-    $args{Passphrase} = GetPassphrase( Address => $args{Key} )
+    $args{Passphrase} = GetPassphrase( Address => $args{Signer} )
         unless defined $args{'Passphrase'};
     $gnupg->passphrase( $args{'Passphrase'} );
 
@@ -572,7 +572,7 @@ sub SignEncryptRFC3156 {
 
         my @signature;
         %res = CallGnuPG(
-            Key        => $args{'Signer'},
+            Signer     => $args{'Signer'},
             Method     => "detach_sign",
             Handles    => { stdin => IO::Handle::CRLF->new },
             Direct     => [],
@@ -605,7 +605,7 @@ sub SignEncryptRFC3156 {
 
         $entity->make_multipart( 'mixed', Force => 1 );
         %res = CallGnuPG(
-            Key        => $args{'Signer'},
+            Signer     => $args{'Signer'},
             Recipients => \@recipients,
             Method     => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
             Handles    => { stdout => $tmp_fh },
@@ -677,7 +677,7 @@ sub _SignEncryptTextInline {
 
     my $entity = $args{'Entity'};
     my %res = CallGnuPG(
-        Key        => $args{'Signer'},
+        Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Method     => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
@@ -718,7 +718,7 @@ sub _SignEncryptAttachmentInline {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Key        => $args{'Signer'},
+        Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Method     => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
@@ -771,7 +771,7 @@ sub SignEncryptContent {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Key        => $args{'Signer'},
+        Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Method     => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'

commit f37f58b444ee3191dcdce55f51771a1b7bcfad15
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jul 27 13:38:09 2011 -0400

    Rename Method to Command for clarity; "--foo" is not a "method"

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 547f93d..b3f4894 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -375,7 +375,7 @@ sub CallGnuPG {
         Recipients  => [],
         Passphrase  => undef,
 
-        Method      => undef,
+        Command     => undef,
         CommandArgs => [],
 
         Content     => undef,
@@ -392,7 +392,7 @@ sub CallGnuPG {
     %handle = %$handle_list;
 
     my $content = $args{Content};
-    my $method  = $args{Method};
+    my $command = $args{Command};
 
     my %GnuPGOptions = RT->Config->Get('GnuPGOptions');
     my %opt = (
@@ -423,14 +423,14 @@ sub CallGnuPG {
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';
         my $pid = safe_run_child {
-            if ($method =~ /^--/) {
+            if ($command =~ /^--/) {
                 $gnupg->wrap_call(
                     handles      => $handles,
-                    commands     => [$method],
+                    commands     => [$command],
                     command_args => $args{CommandArgs},
                 );
             } else {
-                $gnupg->$method(
+                $gnupg->$command(
                     handles      => $handles,
                     command_args => $args{CommandArgs},
                 );
@@ -573,7 +573,7 @@ sub SignEncryptRFC3156 {
         my @signature;
         %res = CallGnuPG(
             Signer     => $args{'Signer'},
-            Method     => "detach_sign",
+            Command    => "detach_sign",
             Handles    => { stdin => IO::Handle::CRLF->new },
             Direct     => [],
             Passphrase => $args{'Passphrase'},
@@ -607,7 +607,7 @@ sub SignEncryptRFC3156 {
         %res = CallGnuPG(
             Signer     => $args{'Signer'},
             Recipients => \@recipients,
-            Method     => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
+            Command    => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
             Handles    => { stdout => $tmp_fh },
             Passphrase => $args{'Passphrase'},
             Content    => $entity->parts(0),
@@ -679,7 +679,7 @@ sub _SignEncryptTextInline {
     my %res = CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
-        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+        Command    => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
                       : ( $args{'Sign'}
                         ? 'clearsign'
@@ -720,7 +720,7 @@ sub _SignEncryptAttachmentInline {
     my %res = CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
-        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+        Command    => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
                       : ( $args{'Sign'}
                         ? 'detach_sign'
@@ -773,7 +773,7 @@ sub SignEncryptContent {
     my %res = CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
-        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+        Command    => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
                       : ( $args{'Sign'}
                         ? 'clearsign'
@@ -1021,7 +1021,7 @@ sub VerifyAttachment {
     $tmp_fh->flush;
 
     return CallGnuPG(
-        Method      => "verify",
+        Command     => "verify",
         CommandArgs => [ '-', $tmp_fn ],
         Passphrase  => $args{'Passphrase'},
         Content     => $args{'Signature'}->bodyhandle,
@@ -1037,7 +1037,7 @@ sub VerifyRFC3156 {
     $tmp_fh->flush;
 
     return CallGnuPG(
-        Method      => "verify",
+        Command     => "verify",
         CommandArgs => [ '-', $tmp_fn ],
         Passphrase  => $args{'Passphrase'},
         Content     => $args{'Signature'}->bodyhandle,
@@ -1062,7 +1062,7 @@ sub DecryptRFC3156 {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Method      => "decrypt",
+        Command     => "decrypt",
         Handles     => { stdout => $tmp_fh },
         Passphrase  => $args{'Passphrase'},
         Content     => $args{'Data'}->bodyhandle,
@@ -1186,7 +1186,7 @@ sub _DecryptInlineBlock {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Method      => "decrypt",
+        Command     => "decrypt",
         Handles     => { stdout => $tmp_fh, stdin => $args{'BlockHandle'} },
         Passphrase  => $args{'Passphrase'},
     );
@@ -1255,7 +1255,7 @@ sub DecryptContent {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Method      => "decrypt",
+        Command     => "decrypt",
         Handles     => { stdout => $tmp_fh },
         Passphrase  => $args{'Passphrase'},
         Content     => $args{'Content'},
@@ -1807,7 +1807,7 @@ sub GetKeysInfo {
             'fingerprint'     => undef, # show fingerprint
             'fixed-list-mode' => undef, # don't merge uid with keys
         },
-        Method      => $method,
+        Command     => $method,
         ( $email ? (CommandArgs => [$email]) : () ),
         Output      => \@info,
     );
@@ -1971,7 +1971,7 @@ sub DeleteKey {
     my $key = shift;
 
     return CallGnuPG(
-        Method      => "--delete-secret-and-public-key",
+        Command     => "--delete-secret-and-public-key",
         CommandArgs => [$key],
         Callback    => sub {
             my %handle = @_;
@@ -1988,7 +1988,7 @@ sub ImportKey {
     my $key = shift;
 
     return CallGnuPG(
-        Method      => "import_keys",
+        Command     => "import_keys",
         Content     => $key,
     );
 }
diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 05a3827..3ccd017 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1154,7 +1154,7 @@ sub lsign_gnupg_key {
     require RT::Crypt::GnuPG;
 
     return RT::Crypt::GnuPG::CallGnuPG(
-        Method      => '--lsign-key',
+        Command     => '--lsign-key',
         CommandArgs => [$key],
         Callback    => sub {
             my %handle = @_;
@@ -1174,7 +1174,7 @@ sub trust_gnupg_key {
     require RT::Crypt::GnuPG;
 
     return RT::Crypt::GnuPG::CallGnuPG(
-        Method      => '--edit-key',
+        Command     => '--edit-key',
         CommandArgs => [$key],
         Callback    => sub {
             my %handle = @_;

commit fde8a3f6520f736a6781c19fe7593c4a0b166aae
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jul 27 14:08:02 2011 -0400

    Only set the defualt key if we actually have one

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index b3f4894..fce5bc9 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -406,7 +406,8 @@ sub CallGnuPG {
     );
     $gnupg->options->armor( 1 );
     $gnupg->options->meta_interactive( 0 );
-    $gnupg->options->default_key( $args{Signer} );
+    $gnupg->options->default_key( $args{Signer} )
+        if defined $args{Signer};
 
     my %seen;
     $gnupg->options->push_recipients( $_ ) for

commit ae6d549c42430b8a01625e63ea1f3aeb978871d5
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jul 27 14:11:29 2011 -0400

    Only set the passphrase if we have one

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index fce5bc9..4567b2d 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -419,7 +419,8 @@ sub CallGnuPG {
         unless defined $args{'Passphrase'};
     $args{Passphrase} = GetPassphrase( Address => $args{Signer} )
         unless defined $args{'Passphrase'};
-    $gnupg->passphrase( $args{'Passphrase'} );
+    $gnupg->passphrase( $args{'Passphrase'} )
+        if defined $args{Passphrase};
 
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';

commit cd978dd12587eddf319d0cc53f50650f687b4145
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Aug 30 19:49:43 2011 -0400

    Split IO::Handle::CRLF into its own file in RT::Crypt::GnuPG::CRLFHandle
    
    In doing so, also document the reasons it is necessary.

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 4567b2d..1504688 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -52,6 +52,7 @@ use warnings;
 package RT::Crypt::GnuPG;
 
 use IO::Handle;
+use RT::Crypt::GnuPG::CRLFHandle;
 use GnuPG::Interface;
 use RT::EmailParser ();
 use RT::Util 'safe_run_child', 'mime_recommended_filename';
@@ -573,10 +574,12 @@ sub SignEncryptRFC3156 {
         $entity->make_multipart( 'mixed', Force => 1 );
 
         my @signature;
+        # We use RT::Crypt::GnuPG::CRLFHandle to canonicalize the
+        # MIME::Entity output to use \r\n instead of \n for its newlines
         %res = CallGnuPG(
             Signer     => $args{'Signer'},
             Command    => "detach_sign",
-            Handles    => { stdin => IO::Handle::CRLF->new },
+            Handles    => { stdin => RT::Crypt::GnuPG::CRLFHandle->new },
             Direct     => [],
             Passphrase => $args{'Passphrase'},
             Content    => $entity->parts(0),
@@ -2100,15 +2103,4 @@ sub _make_gpg_handles {
 
 RT::Base->_ImportOverlays();
 
-# helper package to avoid using temp file
-package IO::Handle::CRLF;
-
-use base qw(IO::Handle);
-
-sub print {
-    my ($self, @args) = (@_);
-    s/\r*\n/\x0D\x0A/g foreach @args;
-    return $self->SUPER::print( @args );
-}
-
 1;
diff --git a/lib/RT/Crypt/GnuPG/CRLFHandle.pm b/lib/RT/Crypt/GnuPG/CRLFHandle.pm
new file mode 100644
index 0000000..5f74457
--- /dev/null
+++ b/lib/RT/Crypt/GnuPG/CRLFHandle.pm
@@ -0,0 +1,22 @@
+package RT::Crypt::GnuPG::CRLFHandle;
+use strict;
+use warnings;
+
+use base qw(IO::Handle);
+
+# https://metacpan.org/module/MIME::Tools#Fuzzing-of-CRLF-and-newline-when-encoding-composing
+# means that the output of $entity->print contains lines terminated by
+# "\n"; however, signatures are generated off of the "correct" form of
+# the MIME entity, which uses "\r\n" as the newline separator.  This
+# class, used only when generating signatures, transparently munges "\n"
+# newlines into "\r\n" newlines such that the generated signature is
+# correct for the "\r\n"-newline version of the MIME entity which will
+# eventually be sent over the wire.
+
+sub print {
+    my ($self, @args) = (@_);
+    s/\r*\n/\x0D\x0A/g foreach @args;
+    return $self->SUPER::print( @args );
+}
+
+1;

-----------------------------------------------------------------------


More information about the Rt-commit mailing list