From 8ec8da0491ad89604700b3e29a227966f6d84ba1 Mon Sep 17 00:00:00 2001 From: Perl Tidy Date: Wed, 5 Dec 2018 15:38:52 -0500 Subject: no bug - reformat all the code using the new perltidy rules --- extensions/SecureMail/Config.pm | 32 +- extensions/SecureMail/Extension.pm | 1046 ++++++++++++++++++------------------ extensions/SecureMail/lib/TCT.pm | 112 ++-- 3 files changed, 593 insertions(+), 597 deletions(-) (limited to 'extensions/SecureMail') diff --git a/extensions/SecureMail/Config.pm b/extensions/SecureMail/Config.pm index 8d877a253..5c2dc615a 100644 --- a/extensions/SecureMail/Config.pm +++ b/extensions/SecureMail/Config.pm @@ -28,25 +28,19 @@ use warnings; use constant NAME => 'SecureMail'; use constant REQUIRED_MODULES => [ - { - package => 'Crypt-OpenPGP', - module => 'Crypt::OpenPGP', - # 1.02 added the ability for new() to take KeyRing objects for the - # PubRing argument. - version => '1.02', - # 1.04 hangs - https://rt.cpan.org/Public/Bug/Display.html?id=68018 - # blacklist => [ '1.04' ], - }, - { - package => 'Crypt-SMIME', - module => 'Crypt::SMIME', - version => 0, - }, - { - package => 'HTML-Tree', - module => 'HTML::Tree', - version => 0, - } + { + package => 'Crypt-OpenPGP', + module => 'Crypt::OpenPGP', + + # 1.02 added the ability for new() to take KeyRing objects for the + # PubRing argument. + version => '1.02', + + # 1.04 hangs - https://rt.cpan.org/Public/Bug/Display.html?id=68018 + # blacklist => [ '1.04' ], + }, + {package => 'Crypt-SMIME', module => 'Crypt::SMIME', version => 0,}, + {package => 'HTML-Tree', module => 'HTML::Tree', version => 0,} ]; __PACKAGE__->NAME; diff --git a/extensions/SecureMail/Extension.pm b/extensions/SecureMail/Extension.pm index 2b5e1bdd6..9790c0828 100644 --- a/extensions/SecureMail/Extension.pm +++ b/extensions/SecureMail/Extension.pm @@ -59,12 +59,12 @@ use constant SECURE_ALL => 2; # public_key text in the 'profiles' table - stores public key ############################################################################## sub install_update_db { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $dbh = Bugzilla->dbh; - $dbh->bz_add_column('groups', 'secure_mail', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); - $dbh->bz_add_column('profiles', 'public_key', { TYPE => 'LONGTEXT' }); + my $dbh = Bugzilla->dbh; + $dbh->bz_add_column('groups', 'secure_mail', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); + $dbh->bz_add_column('profiles', 'public_key', {TYPE => 'LONGTEXT'}); } ############################################################################## @@ -72,194 +72,193 @@ sub install_update_db { ############################################################################## BEGIN { - *Bugzilla::Group::secure_mail = \&_group_secure_mail; - *Bugzilla::User::public_key = \&_user_public_key; - *Bugzilla::securemail_groups = \&_securemail_groups; + *Bugzilla::Group::secure_mail = \&_group_secure_mail; + *Bugzilla::User::public_key = \&_user_public_key; + *Bugzilla::securemail_groups = \&_securemail_groups; } sub _group_secure_mail { return $_[0]->{'secure_mail'}; } sub _securemail_groups { - return Bugzilla->dbh->selectcol_arrayref("SELECT name FROM groups WHERE secure_mail = 1") // []; + return Bugzilla->dbh->selectcol_arrayref( + "SELECT name FROM groups WHERE secure_mail = 1") // []; } # We want to lazy-load the public_key. sub _user_public_key { - my $self = shift; - if (!exists $self->{public_key}) { - ($self->{public_key}) = Bugzilla->dbh->selectrow_array( - "SELECT public_key FROM profiles WHERE userid = ?", - undef, - $self->id - ); - } - return $self->{public_key}; + my $self = shift; + if (!exists $self->{public_key}) { + ($self->{public_key}) + = Bugzilla->dbh->selectrow_array( + "SELECT public_key FROM profiles WHERE userid = ?", + undef, $self->id); + } + return $self->{public_key}; } # Make sure generic functions know about the additional fields in the user # and group objects. sub object_columns { - my ($self, $args) = @_; - my $class = $args->{'class'}; - my $columns = $args->{'columns'}; - - if ($class->isa('Bugzilla::Group')) { - my $dbh = Bugzilla->dbh; - if ($dbh->bz_column_info($class->DB_TABLE, 'secure_mail')) { - push @$columns, 'secure_mail'; - } + my ($self, $args) = @_; + my $class = $args->{'class'}; + my $columns = $args->{'columns'}; + + if ($class->isa('Bugzilla::Group')) { + my $dbh = Bugzilla->dbh; + if ($dbh->bz_column_info($class->DB_TABLE, 'secure_mail')) { + push @$columns, 'secure_mail'; } + } } # Plug appropriate validators so we can check the validity of the two # fields created by this extension, when new values are submitted. sub object_validators { - my ($self, $args) = @_; - my %args = %{ $args }; - my ($invocant, $validators) = @args{qw(class validators)}; + my ($self, $args) = @_; + my %args = %{$args}; + my ($invocant, $validators) = @args{qw(class validators)}; - if ($invocant->isa('Bugzilla::Group')) { - $validators->{'secure_mail'} = \&Bugzilla::Object::check_boolean; - } - elsif ($invocant->isa('Bugzilla::User')) { - $validators->{'public_key'} = sub { - my ($self, $value) = @_; - $value = trim($value) || ''; - - return $value if $value eq ''; - - if ($value =~ /PUBLIC KEY/) { - # PGP keys must be ASCII-armoured. - my $tct = Bugzilla::Extension::SecureMail::TCT->new( - public_key => $value, - command => Bugzilla->localconfig->{tct_bin}, - ); - unless ($tct->is_valid->get) { - ThrowUserError( 'securemail_invalid_key', { errstr => 'key is invalid or expired' } ); - } - } - elsif ($value =~ /BEGIN CERTIFICATE/) { - # S/MIME Keys must be in PEM format (Base64-encoded X.509) - # - # Crypt::SMIME seems not to like tainted values - it claims - # they aren't scalars! - trick_taint($value); - - my $smime = Crypt::SMIME->new(); - eval { - $smime->setPublicKey([$value]); - }; - if ($@) { - ThrowUserError('securemail_invalid_key', - { errstr => $@ }); - } - } - else { - ThrowUserError('securemail_invalid_key'); - } - - return $value; - }; - } + if ($invocant->isa('Bugzilla::Group')) { + $validators->{'secure_mail'} = \&Bugzilla::Object::check_boolean; + } + elsif ($invocant->isa('Bugzilla::User')) { + $validators->{'public_key'} = sub { + my ($self, $value) = @_; + $value = trim($value) || ''; + + return $value if $value eq ''; + + if ($value =~ /PUBLIC KEY/) { + + # PGP keys must be ASCII-armoured. + my $tct = Bugzilla::Extension::SecureMail::TCT->new( + public_key => $value, + command => Bugzilla->localconfig->{tct_bin}, + ); + unless ($tct->is_valid->get) { + ThrowUserError('securemail_invalid_key', + {errstr => 'key is invalid or expired'}); + } + } + elsif ($value =~ /BEGIN CERTIFICATE/) { + + # S/MIME Keys must be in PEM format (Base64-encoded X.509) + # + # Crypt::SMIME seems not to like tainted values - it claims + # they aren't scalars! + trick_taint($value); + + my $smime = Crypt::SMIME->new(); + eval { $smime->setPublicKey([$value]); }; + if ($@) { + ThrowUserError('securemail_invalid_key', {errstr => $@}); + } + } + else { + ThrowUserError('securemail_invalid_key'); + } + + return $value; + }; + } } # When creating a 'group' object, set up the secure_mail field appropriately. sub object_before_create { - my ($self, $args) = @_; - my $class = $args->{'class'}; - my $params = $args->{'params'}; + my ($self, $args) = @_; + my $class = $args->{'class'}; + my $params = $args->{'params'}; - if ($class->isa('Bugzilla::Group')) { - $params->{secure_mail} = Bugzilla->cgi->param('secure_mail'); - } + if ($class->isa('Bugzilla::Group')) { + $params->{secure_mail} = Bugzilla->cgi->param('secure_mail'); + } } # On update, make sure the updating process knows about our new columns. sub object_update_columns { - my ($self, $args) = @_; - my $object = $args->{'object'}; - my $columns = $args->{'columns'}; + my ($self, $args) = @_; + my $object = $args->{'object'}; + my $columns = $args->{'columns'}; - if ($object->isa('Bugzilla::Group')) { - # This seems like a convenient moment to extract this value... - $object->set('secure_mail', Bugzilla->cgi->param('secure_mail')); + if ($object->isa('Bugzilla::Group')) { - push(@$columns, 'secure_mail'); - } - elsif ($object->isa('Bugzilla::User')) { - push(@$columns, 'public_key'); - } + # This seems like a convenient moment to extract this value... + $object->set('secure_mail', Bugzilla->cgi->param('secure_mail')); + + push(@$columns, 'secure_mail'); + } + elsif ($object->isa('Bugzilla::User')) { + push(@$columns, 'public_key'); + } } # Handle the setting and changing of the public key. sub user_preferences { - my ($self, $args) = @_; - my $tab = $args->{'current_tab'}; - my $save = $args->{'save_changes'}; - my $handled = $args->{'handled'}; - my $vars = $args->{'vars'}; - my $params = Bugzilla->input_params; - - return unless $tab eq 'securemail'; - - # Create a new user object so we don't mess with the main one, as we - # don't know where it's been... - my $user = new Bugzilla::User(Bugzilla->user->id); - - if ($save) { - $user->set('public_key', $params->{'public_key'}); - $user->update(); - - # Send user a test email - if ($user->public_key) { - _send_test_email($user); - $vars->{'test_email_sent'} = 1; - } + my ($self, $args) = @_; + my $tab = $args->{'current_tab'}; + my $save = $args->{'save_changes'}; + my $handled = $args->{'handled'}; + my $vars = $args->{'vars'}; + my $params = Bugzilla->input_params; + + return unless $tab eq 'securemail'; + + # Create a new user object so we don't mess with the main one, as we + # don't know where it's been... + my $user = new Bugzilla::User(Bugzilla->user->id); + + if ($save) { + $user->set('public_key', $params->{'public_key'}); + $user->update(); + + # Send user a test email + if ($user->public_key) { + _send_test_email($user); + $vars->{'test_email_sent'} = 1; } + } - $vars->{'public_key'} = $user->public_key; + $vars->{'public_key'} = $user->public_key; - # Set the 'handled' scalar reference to true so that the caller - # knows the panel name is valid and that an extension took care of it. - $$handled = 1; + # Set the 'handled' scalar reference to true so that the caller + # knows the panel name is valid and that an extension took care of it. + $$handled = 1; } sub template_before_process { - my ($self, $args) = @_; - my $file = $args->{'file'}; - my $vars = $args->{'vars'}; - - # Bug dependency emails contain the subject of the dependent bug - # right before the diffs when a status has gone from open/closed - # or closed/open. We need to sanitize the subject of change.blocker - # similar to how we do referenced bugs - return unless - $file eq 'email/bugmail.html.tmpl' - || $file eq 'email/bugmail.txt.tmpl'; - - if (defined $vars->{diffs}) { - foreach my $change (@{ $vars->{diffs} }) { - next if !defined $change->{blocker}; - if (grep($_->secure_mail, @{ $change->{blocker}->groups_in })) { - $change->{blocker}->{short_desc} = "(Secure bug)"; - } - } + my ($self, $args) = @_; + my $file = $args->{'file'}; + my $vars = $args->{'vars'}; + + # Bug dependency emails contain the subject of the dependent bug + # right before the diffs when a status has gone from open/closed + # or closed/open. We need to sanitize the subject of change.blocker + # similar to how we do referenced bugs + return + unless $file eq 'email/bugmail.html.tmpl' + || $file eq 'email/bugmail.txt.tmpl'; + + if (defined $vars->{diffs}) { + foreach my $change (@{$vars->{diffs}}) { + next if !defined $change->{blocker}; + if (grep($_->secure_mail, @{$change->{blocker}->groups_in})) { + $change->{blocker}->{short_desc} = "(Secure bug)"; + } } + } } sub _send_test_email { - my ($user) = @_; - my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'}); + my ($user) = @_; + my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'}); - my $vars = { - to_user => $user->email, - }; + my $vars = {to_user => $user->email,}; - my $msg = ""; - $template->process("account/email/securemail-test.txt.tmpl", $vars, \$msg) - || ThrowTemplateError($template->error()); + my $msg = ""; + $template->process("account/email/securemail-test.txt.tmpl", $vars, \$msg) + || ThrowTemplateError($template->error()); - MessageToMTA($msg); + MessageToMTA($msg); } ############################################################################## @@ -268,365 +267,382 @@ sub _send_test_email { # determine if the bug should be encrypted at the time it is generated sub bugmail_enqueue { - my ($self, $args) = @_; - my $vars = $args->{vars}; - if (_should_secure_bug($vars->{bug})) { - $vars->{bugzilla_encrypt} = 1; - } + my ($self, $args) = @_; + my $vars = $args->{vars}; + if (_should_secure_bug($vars->{bug})) { + $vars->{bugzilla_encrypt} = 1; + } } sub bugmail_generate { - my ($self, $args) = @_; - my $vars = $args->{vars}; - my $email = $args->{email}; - if ($vars->{bugzilla_encrypt}) { - $email->header_set('X-Bugzilla-Encrypt', 1); - } + my ($self, $args) = @_; + my $vars = $args->{vars}; + my $email = $args->{email}; + if ($vars->{bugzilla_encrypt}) { + $email->header_set('X-Bugzilla-Encrypt', 1); + } } sub mailer_before_send { - my ($self, $args) = @_; - - my $email = $args->{'email'}; - my $body = $email->body; - - # Decide whether to make secure. - # This is a bit of a hack; it would be nice if it were more clear - # what sort a particular email is. - my $is_bugmail = $email->header('X-Bugzilla-Status') || - $email->header('X-Bugzilla-Type') eq 'request'; - my $is_passwordmail = !$is_bugmail && ($body =~ /cfmpw.*cxlpw/s); - my $is_test_email = $email->header('X-Bugzilla-Type') =~ /securemail-test/ ? 1 : 0; - my $is_whine_email = $email->header('X-Bugzilla-Type') eq 'whine' ? 1 : 0; - my $encrypt_header = $email->header('X-Bugzilla-Encrypt') ? 1 : 0; - - if ($is_bugmail - || $is_passwordmail - || $is_test_email - || $is_whine_email - || $encrypt_header - ) { - # Convert the email's To address into a User object - my $login = $email->header('To'); - my $emailsuffix = Bugzilla->params->{'emailsuffix'}; - $login =~ s/$emailsuffix$//; - my $user = new Bugzilla::User({ name => $login }); - - # Default to secure. (Of course, this means if this extension has a - # bug, lots of people are going to get bugmail falsely claiming their - # bugs are secure and they need to add a key...) - my $make_secure = SECURE_ALL; - - if ($is_bugmail) { - # This is also a bit of a hack, but there's no header with the - # bug ID in. So we take the first number in the subject. - my ($bug_id) = ($email->header('Subject') =~ /\[\D+(\d+)\]/); - my $bug = new Bugzilla::Bug($bug_id); - if (!_should_secure_bug($bug)) { - $make_secure = SECURE_NONE; - } - # If the insider group has securemail enabled.. - my $insider_group = Bugzilla::Group->new({ name => Bugzilla->params->{'insidergroup'} }); - if ($insider_group - && $insider_group->secure_mail - && $make_secure == SECURE_NONE) - { - my $comment_is_private = Bugzilla->dbh->selectcol_arrayref( - "SELECT isprivate FROM longdescs WHERE bug_id=? ORDER BY bug_when", - undef, $bug_id); - # Encrypt if there are private comments on an otherwise public bug - if (scalar $email->parts > 1) { - $email->walk_parts(sub { - my $part = shift; - my $content_type = $part->content_type; - $body = $part->body if $content_type && $content_type =~ /^text\/plain/; - }); - } - while ($body =~ /[\r\n]--- Comment #(\d+)/g) { - my $comment_number = $1; - if ($comment_number && $comment_is_private->[$comment_number]) { - $make_secure = SECURE_BODY; - last; - } - } - # Encrypt if updating a private attachment without a comment - if ($email->header('X-Bugzilla-Changed-Fields') - && $email->header('X-Bugzilla-Attach-ID')) - { - my $attachment = Bugzilla::Attachment->new($email->header('X-Bugzilla-Attach-ID')); - if ($attachment && $attachment->isprivate) { - $make_secure = SECURE_BODY; - } - } - } - } - elsif ($is_passwordmail) { - # Mail is made unsecure only if the user does not have a public - # key and is not in any security groups. So specifying a public - # key OR being in a security group means the mail is kept secure - # (but, as noted above, the check is the other way around because - # we default to secure). - if ($user && - !$user->public_key && - !grep($_->secure_mail, @{ $user->groups })) - { - $make_secure = SECURE_NONE; - } - } - elsif ($is_whine_email) { - # When a whine email has one or more secure bugs in the body, then - # encrypt the entire email body. Subject can be left alone as it - # comes from the whine settings. - $make_secure = _should_secure_whine($email) ? SECURE_BODY : SECURE_NONE; + my ($self, $args) = @_; + + my $email = $args->{'email'}; + my $body = $email->body; + + # Decide whether to make secure. + # This is a bit of a hack; it would be nice if it were more clear + # what sort a particular email is. + my $is_bugmail = $email->header('X-Bugzilla-Status') + || $email->header('X-Bugzilla-Type') eq 'request'; + my $is_passwordmail = !$is_bugmail && ($body =~ /cfmpw.*cxlpw/s); + my $is_test_email + = $email->header('X-Bugzilla-Type') =~ /securemail-test/ ? 1 : 0; + my $is_whine_email = $email->header('X-Bugzilla-Type') eq 'whine' ? 1 : 0; + my $encrypt_header = $email->header('X-Bugzilla-Encrypt') ? 1 : 0; + + if ( $is_bugmail + || $is_passwordmail + || $is_test_email + || $is_whine_email + || $encrypt_header) + { + # Convert the email's To address into a User object + my $login = $email->header('To'); + my $emailsuffix = Bugzilla->params->{'emailsuffix'}; + $login =~ s/$emailsuffix$//; + my $user = new Bugzilla::User({name => $login}); + + # Default to secure. (Of course, this means if this extension has a + # bug, lots of people are going to get bugmail falsely claiming their + # bugs are secure and they need to add a key...) + my $make_secure = SECURE_ALL; + + if ($is_bugmail) { + + # This is also a bit of a hack, but there's no header with the + # bug ID in. So we take the first number in the subject. + my ($bug_id) = ($email->header('Subject') =~ /\[\D+(\d+)\]/); + my $bug = new Bugzilla::Bug($bug_id); + if (!_should_secure_bug($bug)) { + $make_secure = SECURE_NONE; + } + + # If the insider group has securemail enabled.. + my $insider_group + = Bugzilla::Group->new({name => Bugzilla->params->{'insidergroup'}}); + if ( $insider_group + && $insider_group->secure_mail + && $make_secure == SECURE_NONE) + { + my $comment_is_private + = Bugzilla->dbh->selectcol_arrayref( + "SELECT isprivate FROM longdescs WHERE bug_id=? ORDER BY bug_when", + undef, $bug_id); + + # Encrypt if there are private comments on an otherwise public bug + if (scalar $email->parts > 1) { + $email->walk_parts(sub { + my $part = shift; + my $content_type = $part->content_type; + $body = $part->body if $content_type && $content_type =~ /^text\/plain/; + }); } - elsif ($encrypt_header) { - # Templates or code may set the X-Bugzilla-Encrypt header to - # trigger encryption of emails. Remove that header from the email. - $email->header_set('X-Bugzilla-Encrypt'); + while ($body =~ /[\r\n]--- Comment #(\d+)/g) { + my $comment_number = $1; + if ($comment_number && $comment_is_private->[$comment_number]) { + $make_secure = SECURE_BODY; + last; + } } - # If finding the user fails for some reason, but we determine we - # should be encrypting, we want to make the mail safe. An empty key - # does that. - my $public_key = $user ? $user->public_key : ''; - - # Check if the new bugmail prefix should be added to the subject. - my $add_new = ($email->header('X-Bugzilla-Type') eq 'new' && - $user && - $user->settings->{'bugmail_new_prefix'}->{'value'} eq 'on') ? 1 : 0; - - if ($make_secure == SECURE_NONE) { - # Filter the bug_links in HTML email in case the bugs the links - # point are "secured" bugs and the user may not be able to see - # the summaries. - _filter_bug_links($email); - } - else { - _make_secure($email, $public_key, $is_bugmail && $make_secure == SECURE_ALL, $add_new); + # Encrypt if updating a private attachment without a comment + if ( $email->header('X-Bugzilla-Changed-Fields') + && $email->header('X-Bugzilla-Attach-ID')) + { + my $attachment + = Bugzilla::Attachment->new($email->header('X-Bugzilla-Attach-ID')); + if ($attachment && $attachment->isprivate) { + $make_secure = SECURE_BODY; + } } + } + } + elsif ($is_passwordmail) { + + # Mail is made unsecure only if the user does not have a public + # key and is not in any security groups. So specifying a public + # key OR being in a security group means the mail is kept secure + # (but, as noted above, the check is the other way around because + # we default to secure). + if ($user && !$user->public_key && !grep($_->secure_mail, @{$user->groups})) { + $make_secure = SECURE_NONE; + } + } + elsif ($is_whine_email) { + + # When a whine email has one or more secure bugs in the body, then + # encrypt the entire email body. Subject can be left alone as it + # comes from the whine settings. + $make_secure = _should_secure_whine($email) ? SECURE_BODY : SECURE_NONE; + } + elsif ($encrypt_header) { + + # Templates or code may set the X-Bugzilla-Encrypt header to + # trigger encryption of emails. Remove that header from the email. + $email->header_set('X-Bugzilla-Encrypt'); + } + + # If finding the user fails for some reason, but we determine we + # should be encrypting, we want to make the mail safe. An empty key + # does that. + my $public_key = $user ? $user->public_key : ''; + + # Check if the new bugmail prefix should be added to the subject. + my $add_new + = ( $email->header('X-Bugzilla-Type') eq 'new' + && $user + && $user->settings->{'bugmail_new_prefix'}->{'value'} eq 'on') ? 1 : 0; + + if ($make_secure == SECURE_NONE) { + + # Filter the bug_links in HTML email in case the bugs the links + # point are "secured" bugs and the user may not be able to see + # the summaries. + _filter_bug_links($email); + } + else { + _make_secure($email, $public_key, $is_bugmail && $make_secure == SECURE_ALL, + $add_new); } + } } # Custom hook for bugzilla.mozilla.org (see bug 752400) sub bugmail_referenced_bugs { - my ($self, $args) = @_; - # Sanitise subjects of referenced bugs. - my $referenced_bugs = $args->{'referenced_bugs'}; - # No need to sanitise subjects if the entire email will be secured. - return if _should_secure_bug($args->{'updated_bug'}); - # Replace the subject if required - foreach my $ref (@$referenced_bugs) { - if (grep($_->secure_mail, @{ $ref->{'bug'}->groups_in })) { - $ref->{'short_desc'} = "(Secure bug)"; - } + my ($self, $args) = @_; + + # Sanitise subjects of referenced bugs. + my $referenced_bugs = $args->{'referenced_bugs'}; + + # No need to sanitise subjects if the entire email will be secured. + return if _should_secure_bug($args->{'updated_bug'}); + + # Replace the subject if required + foreach my $ref (@$referenced_bugs) { + if (grep($_->secure_mail, @{$ref->{'bug'}->groups_in})) { + $ref->{'short_desc'} = "(Secure bug)"; } + } } sub _should_secure_bug { - my ($bug) = @_; - # If there's a problem with the bug, err on the side of caution and mark it - # as secure. - return - !$bug - || $bug->{'error'} - || grep($_->secure_mail, @{ $bug->groups_in }); + my ($bug) = @_; + + # If there's a problem with the bug, err on the side of caution and mark it + # as secure. + return !$bug || $bug->{'error'} || grep($_->secure_mail, @{$bug->groups_in}); } sub _should_secure_whine { - my ($email) = @_; - my $should_secure = 0; - $email->walk_parts(sub { - my $part = shift; - my $content_type = $part->content_type; - return if !$content_type || $content_type !~ /^text\/plain/; - my $body = $part->body; - my @bugids = $body =~ /Bug (\d+):/g; - foreach my $id (@bugids) { - $id = trim($id); - next if !$id; - my $bug = new Bugzilla::Bug($id); - if ($bug && _should_secure_bug($bug)) { - $should_secure = 1; - last; - } - } - }); - return $should_secure ? 1 : 0; + my ($email) = @_; + my $should_secure = 0; + $email->walk_parts(sub { + my $part = shift; + my $content_type = $part->content_type; + return if !$content_type || $content_type !~ /^text\/plain/; + my $body = $part->body; + my @bugids = $body =~ /Bug (\d+):/g; + foreach my $id (@bugids) { + $id = trim($id); + next if !$id; + my $bug = new Bugzilla::Bug($id); + if ($bug && _should_secure_bug($bug)) { + $should_secure = 1; + last; + } + } + }); + return $should_secure ? 1 : 0; } sub _make_secure { - my ($email, $key, $sanitise_subject, $add_new) = @_; - - # Add header showing this email has been secured - $email->header_set('X-Bugzilla-Secure-Email', 'Yes'); - - my $subject = $email->header('Subject'); - my ($bug_id) = $subject =~ /\[\D+(\d+)\]/; - - my $key_type = 0; - if ($key && $key =~ /PUBLIC KEY/) { - $key_type = 'PGP'; + my ($email, $key, $sanitise_subject, $add_new) = @_; + + # Add header showing this email has been secured + $email->header_set('X-Bugzilla-Secure-Email', 'Yes'); + + my $subject = $email->header('Subject'); + my ($bug_id) = $subject =~ /\[\D+(\d+)\]/; + + my $key_type = 0; + if ($key && $key =~ /PUBLIC KEY/) { + $key_type = 'PGP'; + } + elsif ($key && $key =~ /BEGIN CERTIFICATE/) { + $key_type = 'S/MIME'; + } + + if ($key_type eq 'PGP') { + ################## + # PGP Encryption # + ################## + + my $tct = Bugzilla::Extension::SecureMail::TCT->new( + public_key => $key, + command => Bugzilla->localconfig->{tct_bin}, + ); + + if (scalar $email->parts > 1) { + my $old_boundary = $email->{ct}{attributes}{boundary}; + my $to_encrypt = "Content-Type: " . $email->content_type . "\n\n"; + + # We need to do some fix up of each part for proper encoding and then + # stringify all parts for encrypting. We have to retain the old + # boundaries as well so that the email client can reconstruct the + # original message properly. + $email->walk_parts(\&_fix_encoding); + + $email->walk_parts(sub { + my ($part) = @_; + if ($sanitise_subject) { + _insert_subject($part, $subject); + } + return if $part->parts > 1; # Top-level + $to_encrypt .= "--$old_boundary\n" . $part->as_string . "\n"; + }); + $to_encrypt .= "--$old_boundary--"; + + # Now create the new properly formatted PGP parts containing the + # encrypted original message + my @new_parts = ( + Email::MIME->create( + attributes => + {content_type => 'application/pgp-encrypted', encoding => '7bit',}, + body => "Version: 1\n", + ), + Email::MIME->create( + attributes => { + content_type => 'application/octet-stream', + filename => 'encrypted.asc', + disposition => 'inline', + encoding => '7bit', + }, + body => _tct_encrypt($tct, $to_encrypt, $bug_id) + ), + ); + $email->parts_set(\@new_parts); + my $new_boundary = $email->{ct}{attributes}{boundary}; + + # Redo the old content type header with the new boundaries + # and other information needed for PGP + $email->header_set("Content-Type", + "multipart/encrypted; " + . "protocol=\"application/pgp-encrypted\"; " + . "boundary=\"$new_boundary\""); } - elsif ($key && $key =~ /BEGIN CERTIFICATE/) { - $key_type = 'S/MIME'; + else { + _fix_encoding($email); + if ($sanitise_subject) { + _insert_subject($email, $subject); + } + $email->body_set(_tct_encrypt($tct, $email->body, $bug_id)); } + } - if ($key_type eq 'PGP') { - ################## - # PGP Encryption # - ################## + elsif ($key_type eq 'S/MIME') { + ##################### + # S/MIME Encryption # + ##################### - my $tct = Bugzilla::Extension::SecureMail::TCT->new( - public_key => $key, - command => Bugzilla->localconfig->{tct_bin}, - ); + $email->walk_parts(\&_fix_encoding); - if (scalar $email->parts > 1) { - my $old_boundary = $email->{ct}{attributes}{boundary}; - my $to_encrypt = "Content-Type: " . $email->content_type . "\n\n"; - - # We need to do some fix up of each part for proper encoding and then - # stringify all parts for encrypting. We have to retain the old - # boundaries as well so that the email client can reconstruct the - # original message properly. - $email->walk_parts(\&_fix_encoding); - - $email->walk_parts(sub { - my ($part) = @_; - if ($sanitise_subject) { - _insert_subject($part, $subject); - } - return if $part->parts > 1; # Top-level - $to_encrypt .= "--$old_boundary\n" . $part->as_string . "\n"; - }); - $to_encrypt .= "--$old_boundary--"; - - # Now create the new properly formatted PGP parts containing the - # encrypted original message - my @new_parts = ( - Email::MIME->create( - attributes => { - content_type => 'application/pgp-encrypted', - encoding => '7bit', - }, - body => "Version: 1\n", - ), - Email::MIME->create( - attributes => { - content_type => 'application/octet-stream', - filename => 'encrypted.asc', - disposition => 'inline', - encoding => '7bit', - }, - body => _tct_encrypt($tct, $to_encrypt, $bug_id) - ), - ); - $email->parts_set(\@new_parts); - my $new_boundary = $email->{ct}{attributes}{boundary}; - # Redo the old content type header with the new boundaries - # and other information needed for PGP - $email->header_set("Content-Type", - "multipart/encrypted; " . - "protocol=\"application/pgp-encrypted\"; " . - "boundary=\"$new_boundary\""); - } - else { - _fix_encoding($email); - if ($sanitise_subject) { - _insert_subject($email, $subject); - } - $email->body_set(_tct_encrypt($tct, $email->body, $bug_id)); - } + if ($sanitise_subject) { + $email->walk_parts(sub { _insert_subject($_[0], $subject) }); } - elsif ($key_type eq 'S/MIME') { - ##################### - # S/MIME Encryption # - ##################### + my $smime = Crypt::SMIME->new(); + my $encrypted; - $email->walk_parts(\&_fix_encoding); + eval { + $smime->setPublicKey([$key]); + $encrypted = $smime->encrypt($email->as_string()); + }; - if ($sanitise_subject) { - $email->walk_parts(sub { _insert_subject($_[0], $subject) }); - } + if (!$@) { - my $smime = Crypt::SMIME->new(); - my $encrypted; - - eval { - $smime->setPublicKey([$key]); - $encrypted = $smime->encrypt($email->as_string()); - }; - - if (!$@) { - # We can't replace the Email::MIME object, so we have to swap - # out its component parts. - my $enc_obj = new Email::MIME($encrypted); - $email->header_obj_set($enc_obj->header_obj()); - $email->parts_set([]); - $email->body_set($enc_obj->body()); - $email->content_type_set('application/pkcs7-mime'); - $email->charset_set('UTF-8') if Bugzilla->params->{'utf8'}; - } - else { - $email->body_set('Error during Encryption: ' . $@); - } + # We can't replace the Email::MIME object, so we have to swap + # out its component parts. + my $enc_obj = new Email::MIME($encrypted); + $email->header_obj_set($enc_obj->header_obj()); + $email->parts_set([]); + $email->body_set($enc_obj->body()); + $email->content_type_set('application/pkcs7-mime'); + $email->charset_set('UTF-8') if Bugzilla->params->{'utf8'}; } else { - # No encryption key provided; send a generic, safe email. - my $template = Bugzilla->template; - my $message; - my $email_type = $email->header('X-Bugzilla-Type'); - my $vars = { - 'urlbase' => Bugzilla->localconfig->{urlbase}, - 'bug_id' => $bug_id, - 'maintainer' => Bugzilla->params->{'maintainer'}, - 'email_type' => $email_type - }; - - $template->process('account/email/encryption-required.txt.tmpl', - $vars, \$message) - || ThrowTemplateError($template->error()); - - $email->parts_set([]); - $email->content_type_set('text/plain'); - $email->body_set($message); + $email->body_set('Error during Encryption: ' . $@); } + } + else { + # No encryption key provided; send a generic, safe email. + my $template = Bugzilla->template; + my $message; + my $email_type = $email->header('X-Bugzilla-Type'); + my $vars = { + 'urlbase' => Bugzilla->localconfig->{urlbase}, + 'bug_id' => $bug_id, + 'maintainer' => Bugzilla->params->{'maintainer'}, + 'email_type' => $email_type + }; - if ($sanitise_subject) { - # This is designed to still work if the admin changes the word - # 'bug' to something else. However, it could break if they change - # the format of the subject line in another way. - my $new = $add_new ? ' New:' : ''; - my $product = $email->header('X-Bugzilla-Product'); - my $component = $email->header('X-Bugzilla-Component'); - # Note: the $bug_id is required within the parentheses in order to keep - # gmail's threading algorithm happy. - $subject =~ s/($bug_id\])\s+(.*)$/$1$new (Secure bug $bug_id in $product :: $component)/; - { - # avoid excessive line wrapping done by Encode. - local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998; - $email->header_set('Subject', encode('MIME-Q', $subject)); - } + $template->process('account/email/encryption-required.txt.tmpl', + $vars, \$message) + || ThrowTemplateError($template->error()); + + $email->parts_set([]); + $email->content_type_set('text/plain'); + $email->body_set($message); + } + + if ($sanitise_subject) { + + # This is designed to still work if the admin changes the word + # 'bug' to something else. However, it could break if they change + # the format of the subject line in another way. + my $new = $add_new ? ' New:' : ''; + my $product = $email->header('X-Bugzilla-Product'); + my $component = $email->header('X-Bugzilla-Component'); + + # Note: the $bug_id is required within the parentheses in order to keep + # gmail's threading algorithm happy. + $subject + =~ s/($bug_id\])\s+(.*)$/$1$new (Secure bug $bug_id in $product :: $component)/; + { + # avoid excessive line wrapping done by Encode. + local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998; + $email->header_set('Subject', encode('MIME-Q', $subject)); } + } } sub _tct_encrypt { - my ($tct, $text, $bug_id) = @_; - - my $comment = Bugzilla->localconfig->{urlbase} . ( $bug_id ? 'show_bug.cgi?id=' . $bug_id : '' ); - my $encrypted; - my $ok = eval { $encrypted = $tct->encrypt( $text, $comment )->get; 1 }; - if (!$ok) { - WARN("Error: $@"); - $encrypted = "$comment\nOpenPGP Encryption failed. Check if your key is expired."; - } - elsif (!$encrypted) { - WARN('message empty!'); - $encrypted = "$comment\nOpenPGP Encryption failed for unknown reason."; - } - - return $encrypted; + my ($tct, $text, $bug_id) = @_; + + my $comment = Bugzilla->localconfig->{urlbase} + . ($bug_id ? 'show_bug.cgi?id=' . $bug_id : ''); + my $encrypted; + my $ok = eval { $encrypted = $tct->encrypt($text, $comment)->get; 1 }; + if (!$ok) { + WARN("Error: $@"); + $encrypted + = "$comment\nOpenPGP Encryption failed. Check if your key is expired."; + } + elsif (!$encrypted) { + WARN('message empty!'); + $encrypted = "$comment\nOpenPGP Encryption failed for unknown reason."; + } + + return $encrypted; } # Insert the subject into the part's body, as the subject of the message will @@ -634,69 +650,67 @@ sub _tct_encrypt { # XXX this incorrectly assumes all parts of the message are the body # we should only alter parts whose parent is multipart/alternative sub _insert_subject { - my ($part, $subject) = @_; - my $content_type = $part->content_type or return; - if ($content_type =~ /^text\/plain/) { - $part->body_str_set("Subject: $subject\015\012\015\012" . $part->body_str); - } - elsif ($content_type =~ /^text\/html/) { - my $tree = HTML::Tree->new->parse_content($part->body_str); - my $body = $tree->look_down(qw(_tag body)); - $body->unshift_content(['strong', "Subject: $subject"], ['br']); - $part->body_str_set($tree->as_HTML); - $tree->delete; - } + my ($part, $subject) = @_; + my $content_type = $part->content_type or return; + if ($content_type =~ /^text\/plain/) { + $part->body_str_set("Subject: $subject\015\012\015\012" . $part->body_str); + } + elsif ($content_type =~ /^text\/html/) { + my $tree = HTML::Tree->new->parse_content($part->body_str); + my $body = $tree->look_down(qw(_tag body)); + $body->unshift_content(['strong', "Subject: $subject"], ['br']); + $part->body_str_set($tree->as_HTML); + $tree->delete; + } } sub _fix_encoding { - my $part = shift; - - # don't touch the top-level part of multi-part mail - return if $part->parts > 1; - - # nothing to do if the part already has a charset - my $ct = parse_content_type($part->content_type); - my $charset = $ct->{attributes}{charset} - ? $ct->{attributes}{charset} - : ''; - return unless !$charset || $charset eq 'us-ascii'; - - if (Bugzilla->params->{utf8}) { - $part->charset_set('UTF-8'); - my $raw = $part->body_raw; - if (utf8::is_utf8($raw)) { - utf8::encode($raw); - $part->body_set($raw); - } + my $part = shift; + + # don't touch the top-level part of multi-part mail + return if $part->parts > 1; + + # nothing to do if the part already has a charset + my $ct = parse_content_type($part->content_type); + my $charset = $ct->{attributes}{charset} ? $ct->{attributes}{charset} : ''; + return unless !$charset || $charset eq 'us-ascii'; + + if (Bugzilla->params->{utf8}) { + $part->charset_set('UTF-8'); + my $raw = $part->body_raw; + if (utf8::is_utf8($raw)) { + utf8::encode($raw); + $part->body_set($raw); } - $part->encoding_set('quoted-printable'); + } + $part->encoding_set('quoted-printable'); } sub _filter_bug_links { - my ($email) = @_; - $email->walk_parts(sub { - my $part = shift; - _fix_encoding($part); - my $content_type = $part->content_type; - return if !$content_type || $content_type !~ /text\/html/; - my $tree = HTML::Tree->new->parse_content($part->body_str); - my @links = $tree->look_down( _tag => q{a}, class => qr/bz_bug_link/ ); - my $updated = 0; - foreach my $link (@links) { - my $href = $link->attr('href'); - my ($bug_id) = $href =~ /\Qshow_bug.cgi?id=\E(\d+)/; - my $bug = new Bugzilla::Bug($bug_id); - if ($bug && _should_secure_bug($bug)) { - $link->attr('title', '(secure bug)'); - $link->attr('class', 'bz_bug_link'); - $updated = 1; - } - } - if ($updated) { - $part->body_str_set($tree->as_HTML); - } - $tree->delete; - }); + my ($email) = @_; + $email->walk_parts(sub { + my $part = shift; + _fix_encoding($part); + my $content_type = $part->content_type; + return if !$content_type || $content_type !~ /text\/html/; + my $tree = HTML::Tree->new->parse_content($part->body_str); + my @links = $tree->look_down(_tag => q{a}, class => qr/bz_bug_link/); + my $updated = 0; + foreach my $link (@links) { + my $href = $link->attr('href'); + my ($bug_id) = $href =~ /\Qshow_bug.cgi?id=\E(\d+)/; + my $bug = new Bugzilla::Bug($bug_id); + if ($bug && _should_secure_bug($bug)) { + $link->attr('title', '(secure bug)'); + $link->attr('class', 'bz_bug_link'); + $updated = 1; + } + } + if ($updated) { + $part->body_str_set($tree->as_HTML); + } + $tree->delete; + }); } __PACKAGE__->NAME; diff --git a/extensions/SecureMail/lib/TCT.pm b/extensions/SecureMail/lib/TCT.pm index 3a16309c2..f3de8ca39 100644 --- a/extensions/SecureMail/lib/TCT.pm +++ b/extensions/SecureMail/lib/TCT.pm @@ -15,76 +15,64 @@ use Future::Utils qw(call); use Future; use IO::Async::Process; -has 'public_key' => ( is => 'ro', required => 1 ); -has 'public_key_file' => ( is => 'lazy' ); -has 'is_valid' => ( is => 'lazy' ); -has 'command' => ( is => 'ro', default => 'tct' ); +has 'public_key' => (is => 'ro', required => 1); +has 'public_key_file' => (is => 'lazy'); +has 'is_valid' => (is => 'lazy'); +has 'command' => (is => 'ro', default => 'tct'); sub _build_public_key_file { - my ($self) = @_; - my $fh = File::Temp->new(SUFFIX => '.pubkey'); - $fh->print($self->public_key); - $fh->close; - return $fh; + my ($self) = @_; + my $fh = File::Temp->new(SUFFIX => '.pubkey'); + $fh->print($self->public_key); + $fh->close; + return $fh; } sub _build_is_valid { - my ($self) = @_; - - my $loop = IO::Async::Loop->new; - my $exit_f = $loop->new_future; - my ($stderr, $stdout); - my $process = IO::Async::Process->new( - command => [$self->command, 'check', '-k', $self->public_key_file ], - stderr => { - into => \$stderr, - }, - stdout => { - into => \$stdout, - }, - on_finish => on_finish($exit_f), - on_exception => on_exception($self->command, $exit_f), - ); - $loop->add($process); - - return $exit_f->then( - sub { - my ($rv) = @_; - Future->wrap($rv == 0); - } - ); + my ($self) = @_; + + my $loop = IO::Async::Loop->new; + my $exit_f = $loop->new_future; + my ($stderr, $stdout); + my $process = IO::Async::Process->new( + command => [$self->command, 'check', '-k', $self->public_key_file], + stderr => {into => \$stderr,}, + stdout => {into => \$stdout,}, + on_finish => on_finish($exit_f), + on_exception => on_exception($self->command, $exit_f), + ); + $loop->add($process); + + return $exit_f->then(sub { + my ($rv) = @_; + Future->wrap($rv == 0); + }); } sub encrypt { - my ($self, $input, $comment) = @_; - $self->is_valid->then( - sub { - my ($is_valid) = @_; - call { - die 'invalid public key!' unless $is_valid; - - my $output; - my $loop = IO::Async::Loop->new; - my $exit_f = $loop->new_future; - my @command = ( $self->command, 'encrypt', '-k', $self->public_key_file ); - push @command, '--comment', $comment if $comment; - my $process = IO::Async::Process->new( - command => \@command, - stdin => { - from => $input, - }, - stdout => { - into => \$output, - }, - on_finish => on_finish($exit_f), - on_exception => on_exception($self->command, $exit_f), - ); - $loop->add($process); - - return $exit_f->then(sub { Future->wrap($output) }); - } - } - ); + my ($self, $input, $comment) = @_; + $self->is_valid->then(sub { + my ($is_valid) = @_; + call { + die 'invalid public key!' unless $is_valid; + + my $output; + my $loop = IO::Async::Loop->new; + my $exit_f = $loop->new_future; + my @command = ($self->command, 'encrypt', '-k', $self->public_key_file); + push @command, '--comment', $comment if $comment; + my $process = IO::Async::Process->new( + command => \@command, + stdin => {from => $input,}, + stdout => {into => \$output,}, + on_finish => on_finish($exit_f), + on_exception => on_exception($self->command, $exit_f), + ); + $loop->add($process); + + return $exit_f->then(sub { Future->wrap($output) }); + } + }); } 1; -- cgit v1.2.3-24-g4f1b