X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FCrypt%2FGnuPG.pm;h=03636c8c3d2ff4c3fa6447a9cde167d4a95647e5;hb=ed1f84b4e8f626245995ecda5afcf83092c153b2;hp=bb8b2dbfc4be0870d0d883dd88d07be64295cc8e;hpb=fb4ab1073f0d15d660c6cdc4e07afebf68ef3924;p=freeside.git diff --git a/rt/lib/RT/Crypt/GnuPG.pm b/rt/lib/RT/Crypt/GnuPG.pm index bb8b2dbfc..03636c8c3 100644 --- a/rt/lib/RT/Crypt/GnuPG.pm +++ b/rt/lib/RT/Crypt/GnuPG.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -54,7 +54,7 @@ package RT::Crypt::GnuPG; use IO::Handle; use GnuPG::Interface; use RT::EmailParser (); -use RT::Util 'safe_run_child'; +use RT::Util 'safe_run_child', 'mime_recommended_filename'; =head1 NAME @@ -168,7 +168,7 @@ quoted, otherwise you can see quite cryptic error 'gpg: Invalid option "--0"'. =item --homedir -The GnuPG home directory, by default it is set to F. +The GnuPG home directory, by default it is set to F. You can manage this data with the 'gpg' commandline utility using the GNUPGHOME environment variable or --homedir option. @@ -354,13 +354,13 @@ my %supported_opt = map { $_ => 1 } qw( 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. +# need them, just pass 'IO::Handle->new()' and then close it after safe_run_child. # we don't want to leak anything into FCGI/Apache/MP handles, this break things. # So code should look like: # my $handles = GnuPG::Handles->new( -# stdin => ($handle{'stdin'} = new IO::Handle), -# stdout => ($handle{'stdout'} = new IO::Handle), -# stderr => ($handle{'stderr'} = new IO::Handle), +# stdin => ($handle{'stdin'} = IO::Handle->new()), +# stdout => ($handle{'stdout'} = IO::Handle->new()), +# stderr => ($handle{'stderr'} = IO::Handle->new()), # ... # ); @@ -401,14 +401,15 @@ sub SignEncrypt { my $entity = $args{'Entity'}; if ( $args{'Sign'} && !defined $args{'Signer'} ) { + my @addresses = Email::Address->parse( Encode::decode("UTF-8",$entity->head->get( 'From' ))); $args{'Signer'} = UseKeyForSigning() - || (Email::Address->parse( $entity->head->get( 'From' ) ))[0]->address; + || $addresses[0]->address; } if ( $args{'Encrypt'} && !$args{'Recipients'} ) { my %seen; $args{'Recipients'} = [ grep $_ && !$seen{ $_ }++, map $_->address, - map Email::Address->parse( $entity->head->get( $_ ) ), + map Email::Address->parse( Encode::decode("UTF-8",$entity->head->get( $_ ) ) ), qw(To Cc Bcc) ]; } @@ -435,7 +436,7 @@ sub SignEncryptRFC3156 { @_ ); - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); # handling passphrase in GnuPGOptions @@ -520,7 +521,7 @@ sub SignEncryptRFC3156 { $gnupg->options->push_recipients( $_ ) foreach map UseKeyForEncryption($_) || $_, grep !$seen{ $_ }++, map $_->address, - map Email::Address->parse( $entity->head->get( $_ ) ), + map Email::Address->parse( Encode::decode( "UTF-8", $entity->head->get( $_ ) ) ), qw(To Cc Bcc); my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); @@ -617,7 +618,7 @@ sub _SignEncryptTextInline { ); return unless $args{'Sign'} || $args{'Encrypt'}; - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); # handling passphrase in GnupGOptions @@ -684,7 +685,7 @@ sub _SignEncryptTextInline { return %res; } - $entity->bodyhandle( new MIME::Body::File $tmp_fn ); + $entity->bodyhandle( MIME::Body::File->new( $tmp_fn) ); $entity->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh; return %res; @@ -705,7 +706,7 @@ sub _SignEncryptAttachmentInline { ); return unless $args{'Sign'} || $args{'Encrypt'}; - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); # handling passphrase in GnupGOptions @@ -771,7 +772,7 @@ sub _SignEncryptAttachmentInline { return %res; } - my $filename = $entity->head->recommended_filename || 'no_name'; + my $filename = mime_recommended_filename( $entity ) || 'no_name'; if ( $args{'Sign'} && !$args{'Encrypt'} ) { $entity->make_multipart; $entity->attach( @@ -781,7 +782,7 @@ sub _SignEncryptAttachmentInline { Disposition => 'attachment', ); } else { - $entity->bodyhandle( new MIME::Body::File $tmp_fn ); + $entity->bodyhandle(MIME::Body::File->new( $tmp_fn) ); $entity->effective_type('application/octet-stream'); $entity->head->mime_attr( $_ => "$filename.pgp" ) foreach (qw(Content-Type.name Content-Disposition.filename)); @@ -807,7 +808,7 @@ sub SignEncryptContent { ); return unless $args{'Sign'} || $args{'Encrypt'}; - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); # handling passphrase in GnupGOptions @@ -900,6 +901,33 @@ sub FindProtectedParts { $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-----/; my $type = $1? 'signed': 'encrypted'; @@ -1064,9 +1092,13 @@ sub VerifyDecrypt { } if ( $args{'SetStatus'} || $args{'AddStatus'} ) { my $method = $args{'AddStatus'} ? 'add' : 'set'; + # Let the header be modified so continuations are handled + my $modify = $status_on->head->modify; + $status_on->head->modify(1); $status_on->head->$method( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} ); + $status_on->head->modify($modify); } } foreach my $item( grep $_->{'Type'} eq 'encrypted', @protected ) { @@ -1083,9 +1115,13 @@ sub VerifyDecrypt { } if ( $args{'SetStatus'} || $args{'AddStatus'} ) { my $method = $args{'AddStatus'} ? 'add' : 'set'; + # Let the header be modified so continuations are handled + my $modify = $status_on->head->modify; + $status_on->head->modify(1); $status_on->head->$method( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} ); + $status_on->head->modify($modify); } } return @res; @@ -1096,7 +1132,7 @@ sub VerifyInline { return DecryptInline( @_ ) } sub VerifyAttachment { my %args = ( Data => undef, Signature => undef, Top => undef, @_ ); - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); $opt{'digest-algo'} ||= 'SHA1'; $gnupg->options->hash_init( @@ -1150,7 +1186,7 @@ sub VerifyAttachment { sub VerifyRFC3156 { my %args = ( Data => undef, Signature => undef, Top => undef, @_ ); - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); $opt{'digest-algo'} ||= 'SHA1'; $gnupg->options->hash_init( @@ -1203,7 +1239,7 @@ sub DecryptRFC3156 { @_ ); - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); # handling passphrase in GnupGOptions @@ -1265,7 +1301,7 @@ sub DecryptRFC3156 { } seek $tmp_fh, 0, 0; - my $parser = new RT::EmailParser; + my $parser = RT::EmailParser->new(); my $decrypted = $parser->ParseMIMEEntityFromFileHandle( $tmp_fh, 0 ); $decrypted->{'__store_link_to_object_to_avoid_early_cleanup'} = $parser; $args{'Top'}->parts( [] ); @@ -1281,7 +1317,7 @@ sub DecryptInline { @_ ); - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); # handling passphrase in GnuPGOptions @@ -1377,7 +1413,7 @@ sub DecryptInline { } seek $tmp_fh, 0, 0; - $args{'Data'}->bodyhandle( new MIME::Body::File $tmp_fn ); + $args{'Data'}->bodyhandle(MIME::Body::File->new( $tmp_fn )); $args{'Data'}->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh; return %res; } @@ -1440,7 +1476,7 @@ sub DecryptAttachment { @_ ); - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); # handling passphrase in GnuPGOptions @@ -1473,7 +1509,7 @@ sub DecryptAttachment { ); return %res unless $res_fh; - $args{'Data'}->bodyhandle( new MIME::Body::File $res_fn ); + $args{'Data'}->bodyhandle(MIME::Body::File->new($res_fn) ); $args{'Data'}->{'__store_tmp_handle_to_avoid_early_cleanup'} = $res_fh; my $head = $args{'Data'}->head; @@ -1498,7 +1534,7 @@ sub DecryptContent { @_ ); - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); # handling passphrase in GnupGOptions @@ -1608,8 +1644,7 @@ User friendly message. =back -This parser is based on information from GnuPG distribution, see also -F in the RT distribution. +This parser is based on information from GnuPG distribution. =cut @@ -1684,6 +1719,7 @@ my %ignore_keyword = map { $_ => 1 } qw( BEGIN_ENCRYPTION SIG_ID VALIDSIG ENC_TO BEGIN_DECRYPTION END_DECRYPTION GOODMDC TRUST_UNDEFINED TRUST_NEVER TRUST_MARGINAL TRUST_FULLY TRUST_ULTIMATE + DECRYPTION_INFO ); sub ParseStatus { @@ -2011,7 +2047,7 @@ sub CheckRecipients { # good, one suitable and trusted key next; } - my $user = RT::User->new( $RT::SystemUser ); + my $user = RT::User->new( RT->SystemUser ); $user->LoadByEmail( $address ); # it's possible that we have no User record with the email $user = undef unless $user->id; @@ -2087,7 +2123,7 @@ sub GetKeysInfo { return (exit_code => 0) unless $force; } - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); $opt{'digest-algo'} ||= 'SHA1'; $opt{'with-colons'} = undef; # parseable format @@ -2107,7 +2143,9 @@ sub GetKeysInfo { 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) : () ) }; + my $pid = safe_run_child { $gnupg->$method( handles => $handles, $email + ? (command_args => [ "--", $email]) + : () ) }; close $handle{'stdin'}; waitpid $pid, 0; }; @@ -2123,7 +2161,10 @@ sub GetKeysInfo { } $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{'logger'} && $? ) { + $RT::Logger->error( $res{'logger'} ); + $RT::Logger->error( 'The above error may result from an unconfigured RT/GPG installation. See perldoc etc/RT_Config.pm for information about configuring or disabling GPG support for RT' ); + } if ( $@ || $? ) { $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8); return %res; @@ -2273,7 +2314,7 @@ sub _ParseDate { return $value unless $value; require RT::Date; - my $obj = RT::Date->new( $RT::SystemUser ); + my $obj = RT::Date->new( RT->SystemUser ); # unix time if ( $value =~ /^\d+$/ ) { $obj->Set( Value => $value ); @@ -2286,7 +2327,7 @@ sub _ParseDate { sub DeleteKey { my $key = shift; - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); $gnupg->options->hash_init( _PrepareGnuPGOptions( %opt ), @@ -2298,11 +2339,10 @@ sub DeleteKey { eval { local $SIG{'CHLD'} = 'DEFAULT'; - local @ENV{'LANG', 'LC_ALL'} = ('C', 'C'); my $pid = safe_run_child { $gnupg->wrap_call( handles => $handles, commands => ['--delete-secret-and-public-key'], - command_args => [$key], + command_args => ["--", $key], ) }; close $handle{'stdin'}; while ( my $str = readline $handle{'status'} ) { @@ -2334,7 +2374,7 @@ sub DeleteKey { sub ImportKey { my $key = shift; - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); $gnupg->options->hash_init( _PrepareGnuPGOptions( %opt ), @@ -2346,7 +2386,6 @@ sub ImportKey { eval { local $SIG{'CHLD'} = 'DEFAULT'; - local @ENV{'LANG', 'LC_ALL'} = ('C', 'C'); my $pid = safe_run_child { $gnupg->wrap_call( handles => $handles, commands => ['--import'], @@ -2417,7 +2456,7 @@ properly (and false otherwise). sub Probe { - my $gnupg = new GnuPG::Interface; + my $gnupg = GnuPG::Interface->new(); my %opt = RT->Config->Get('GnuPGOptions'); $gnupg->options->hash_init( _PrepareGnuPGOptions( %opt ),