RT option to exclude certain Cc addresses, #15451
[freeside.git] / rt / lib / RT / Interface / Email.pm
index b669b5b..9216887 100755 (executable)
@@ -1,40 +1,40 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # BEGIN BPS TAGGED BLOCK {{{
-# 
+#
 # COPYRIGHT:
 # COPYRIGHT:
-# 
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-#                                          <jesse@bestpractical.com>
-# 
+#
+# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+#                                          <sales@bestpractical.com>
+#
 # (Except where explicitly superseded by other copyright notices)
 # (Except where explicitly superseded by other copyright notices)
-# 
-# 
+#
+#
 # LICENSE:
 # LICENSE:
-# 
+#
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # been provided with this software, but in any event can be snarfed
 # from www.gnu.org.
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # been provided with this software, but in any event can be snarfed
 # from www.gnu.org.
-# 
+#
 # This work is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # This work is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301 or visit their web page on the internet at
 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301 or visit their web page on the internet at
 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-# 
-# 
+#
+#
 # CONTRIBUTION SUBMISSION POLICY:
 # CONTRIBUTION SUBMISSION POLICY:
-# 
+#
 # (The following paragraph is not intended to limit the rights granted
 # to you to modify and distribute this software under the terms of
 # the GNU General Public License and is only of importance to you if
 # you choose to contribute your changes and enhancements to the
 # community by submitting them to Best Practical Solutions, LLC.)
 # (The following paragraph is not intended to limit the rights granted
 # to you to modify and distribute this software under the terms of
 # the GNU General Public License and is only of importance to you if
 # you choose to contribute your changes and enhancements to the
 # community by submitting them to Best Practical Solutions, LLC.)
-# 
+#
 # By intentionally submitting any modifications, corrections or
 # derivatives to this work, or any other work intended for use with
 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
 # By intentionally submitting any modifications, corrections or
 # derivatives to this work, or any other work intended for use with
 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
@@ -43,7 +43,7 @@
 # royalty-free, perpetual, license to use, copy, create derivative
 # works based on those contributions, and sublicense and distribute
 # those contributions and any derivatives thereof.
 # royalty-free, perpetual, license to use, copy, create derivative
 # works based on those contributions, and sublicense and distribute
 # those contributions and any derivatives thereof.
-# 
+#
 # END BPS TAGGED BLOCK }}}
 
 package RT::Interface::Email;
 # END BPS TAGGED BLOCK }}}
 
 package RT::Interface::Email;
@@ -245,16 +245,23 @@ sub MailError {
         level   => $args{'LogLevel'},
         message => $args{'Explanation'}
     ) if $args{'LogLevel'};
         level   => $args{'LogLevel'},
         message => $args{'Explanation'}
     ) if $args{'LogLevel'};
+
     # the colons are necessary to make ->build include non-standard headers
     # the colons are necessary to make ->build include non-standard headers
-    my $entity = MIME::Entity->build(
-        Type                   => "multipart/mixed",
-        From                   => $args{'From'},
-        Bcc                    => $args{'Bcc'},
-        To                     => $args{'To'},
-        Subject                => $args{'Subject'},
-        'Precedence:'             => 'bulk',
+    my %entity_args = (
+        Type                    => "multipart/mixed",
+        From                    => $args{'From'},
+        Bcc                     => $args{'Bcc'},
+        To                      => $args{'To'},
+        Subject                 => $args{'Subject'},
         'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
     );
         'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
     );
+
+    # only set precedence if the sysadmin wants us to
+    if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) {
+        $entity_args{'Precedence:'} = RT->Config->Get('DefaultErrorMailPrecedence');
+    }
+
+    my $entity = MIME::Entity->build(%entity_args);
     SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} );
 
     $entity->attach( Data => $args{'Explanation'} . "\n" );
     SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} );
 
     $entity->attach( Data => $args{'Explanation'} . "\n" );
@@ -280,6 +287,9 @@ RT's outgoing mail configuration. If C<BOUNCE> is passed, and is a
 true value, the message will be marked as an autogenerated error, if
 possible. Sets Date field of the head to now if it's not set.
 
 true value, the message will be marked as an autogenerated error, if
 possible. Sets Date field of the head to now if it's not set.
 
+If the C<X-RT-Squelch> header is set to any true value, the mail will
+not be sent. One use is to let extensions easily cancel outgoing mail.
+
 Ticket and Transaction arguments are optional. If Transaction is
 specified and Ticket is not then ticket of the transaction is
 used, but only if the transaction belongs to a ticket.
 Ticket and Transaction arguments are optional. If Transaction is
 specified and Ticket is not then ticket of the transaction is
 used, but only if the transaction belongs to a ticket.
@@ -343,6 +353,11 @@ sub SendEmail {
         return -1;
     }
 
         return -1;
     }
 
+    if ($args{'Entity'}->head->get('X-RT-Squelch')) {
+        $RT::Logger->info( $msgid . " Squelch header found. Not sending." );
+        return -1;
+    }
+
     if ( $TransactionObj && !$TicketObj
         && $TransactionObj->ObjectType eq 'RT::Ticket' )
     {
     if ( $TransactionObj && !$TicketObj
         && $TransactionObj->ObjectType eq 'RT::Ticket' )
     {
@@ -379,8 +394,9 @@ sub SendEmail {
 
     my $mail_command = RT->Config->Get('MailCommand');
 
 
     my $mail_command = RT->Config->Get('MailCommand');
 
-    if ($mail_command eq 'testfile') {
+    if ($mail_command eq 'testfile' and not $Mail::Mailer::testfile::config{outfile}) {
         $Mail::Mailer::testfile::config{outfile} = File::Temp->new;
         $Mail::Mailer::testfile::config{outfile} = File::Temp->new;
+        $RT::Logger->info("Storing outgoing emails in $Mail::Mailer::testfile::config{outfile}");
     }
 
     # if it is a sub routine, we just return it;
     }
 
     # if it is a sub routine, we just return it;
@@ -429,7 +445,8 @@ sub SendEmail {
             # don't ignore CHLD signal to get proper exit code
             local $SIG{'CHLD'} = 'DEFAULT';
 
             # don't ignore CHLD signal to get proper exit code
             local $SIG{'CHLD'} = 'DEFAULT';
 
-            open my $mail, "|$path $args" or die "couldn't execute program: $!";
+            open( my $mail, '|-', "$path $args >/dev/null" )
+                or die "couldn't execute program: $!";
 
             # if something wrong with $mail->print we will get PIPE signal, handle it
             local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
 
             # if something wrong with $mail->print we will get PIPE signal, handle it
             local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
@@ -442,10 +459,14 @@ sub SendEmail {
                 my $msg = "$msgid: `$path $args` exitted with code ". ($?>>8);
                 $msg = ", interrupted by signal ". ($?&127) if $?&127;
                 $RT::Logger->error( $msg );
                 my $msg = "$msgid: `$path $args` exitted with code ". ($?>>8);
                 $msg = ", interrupted by signal ". ($?&127) if $?&127;
                 $RT::Logger->error( $msg );
+                die $msg;
             }
         };
         if ( $@ ) {
             $RT::Logger->crit( "$msgid: Could not send mail with command `$path $args`: " . $@ );
             }
         };
         if ( $@ ) {
             $RT::Logger->crit( "$msgid: Could not send mail with command `$path $args`: " . $@ );
+            if ( $TicketObj ) {
+                _RecordSendEmailFailure( $TicketObj );
+            }
             return 0;
         }
     }
             return 0;
         }
     }
@@ -457,6 +478,9 @@ sub SendEmail {
         ) } };
         unless ( $smtp ) {
             $RT::Logger->crit( "Could not connect to SMTP server.");
         ) } };
         unless ( $smtp ) {
             $RT::Logger->crit( "Could not connect to SMTP server.");
+            if ($TicketObj) {
+                _RecordSendEmailFailure( $TicketObj );
+            }
             return 0;
         }
 
             return 0;
         }
 
@@ -485,6 +509,9 @@ sub SendEmail {
 
         unless ( $status ) {
             $RT::Logger->crit( "$msgid: Could not send mail via SMTP." );
 
         unless ( $status ) {
             $RT::Logger->crit( "$msgid: Could not send mail via SMTP." );
+            if ( $TicketObj ) {
+                _RecordSendEmailFailure( $TicketObj );
+            }
             return 0;
         }
     }
             return 0;
         }
     }
@@ -502,6 +529,9 @@ sub SendEmail {
 
         unless ( $args{'Entity'}->send( @mailer_args ) ) {
             $RT::Logger->crit( "$msgid: Could not send mail." );
 
         unless ( $args{'Entity'}->send( @mailer_args ) ) {
             $RT::Logger->crit( "$msgid: Could not send mail." );
+            if ( $TicketObj ) {
+                _RecordSendEmailFailure( $TicketObj );
+            }
             return 0;
         }
     }
             return 0;
         }
     }
@@ -565,7 +595,7 @@ sub SendEmailUsingTemplate {
         return -1;
     }
 
         return -1;
     }
 
-    $mail->head->set( $_ => $args{ $_ } )
+    $mail->head->set( $_ => Encode::encode_utf8( $args{ $_ } ) )
         foreach grep defined $args{$_}, qw(To Cc Bcc From);
 
     SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
         foreach grep defined $args{$_}, qw(To Cc Bcc From);
 
     SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
@@ -963,25 +993,32 @@ sub ParseCcAddressesFromHead {
         @_
     );
 
         @_
     );
 
-    my @recipients =
-        map lc $_->address,
+    my $current_address = lc $args{'CurrentUser'}->EmailAddress;
+    my $user = $args{'CurrentUser'}->UserObj;
+
+    return
+        grep {  $_ ne $current_address 
+                && !RT::EmailParser->IsRTAddress( $_ )
+                && !IgnoreCcAddress( $_ )
+             }
+        map lc $user->CanonicalizeEmailAddress( $_->address ),
         map Email::Address->parse( $args{'Head'}->get( $_ ) ),
         qw(To Cc);
         map Email::Address->parse( $args{'Head'}->get( $_ ) ),
         qw(To Cc);
+}
 
 
-    my @res;
-    foreach my $address ( @recipients ) {
-        $address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress( $address );
-        next if lc $args{'CurrentUser'}->EmailAddress   eq $address;
-        next if lc $args{'QueueObj'}->CorrespondAddress eq $address;
-        next if lc $args{'QueueObj'}->CommentAddress    eq $address;
-        next if RT::EmailParser->IsRTAddress( $address );
+=head2 IgnoreCcAddress ADDRESS
 
 
-        push @res, $address;
-    }
-    return @res;
-}
+Returns true if ADDRESS matches the $IgnoreCcRegexp config variable.
 
 
+=cut
 
 
+sub IgnoreCcAddress {
+    my $address = shift;
+    if ( my $address_re = RT->Config->Get('IgnoreCcRegexp') ) {
+        return 1 if $address =~ /$address_re/i;
+    }
+    return undef;
+}
 
 =head2 ParseSenderAddressFromHead HEAD
 
 
 =head2 ParseSenderAddressFromHead HEAD
 
@@ -1772,9 +1809,21 @@ sub IsCorrectAction {
     return ( 1, @actions );
 }
 
     return ( 1, @actions );
 }
 
-eval "require RT::Interface::Email_Vendor";
-die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm} );
-eval "require RT::Interface::Email_Local";
-die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm} );
+sub _RecordSendEmailFailure {
+    my $ticket = shift;
+    if ($ticket) {
+        $ticket->_RecordNote(
+            NoteType => 'SystemError',
+            Content => "Sending the previous mail has failed.  Please contact your admin, they can find more details in the logs.",
+        );
+        return 1;
+    }
+    else {
+        $RT::Logger->error( "Can't record send email failure as ticket is missing" );
+        return;
+    }
+}
+
+RT::Base->_ImportOverlays();
 
 1;
 
 1;