Merge branch 'patch-1' of https://github.com/gjones2/Freeside
[freeside.git] / rt / lib / RT / Interface / Email.pm
index b669b5b..dda6f70 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-2012 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;
@@ -57,6 +57,7 @@ use RT::EmailParser;
 use File::Temp;
 use UNIVERSAL::require;
 use Mail::Mailer ();
 use File::Temp;
 use UNIVERSAL::require;
 use Mail::Mailer ();
+use Text::ParseWords qw/shellwords/;
 
 BEGIN {
     use base 'Exporter';
 
 BEGIN {
     use base 'Exporter';
@@ -245,16 +246,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 +288,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.
@@ -307,6 +318,35 @@ header field then it's value is used
 
 =cut
 
 
 =cut
 
+sub WillSignEncrypt {
+    my %args = @_;
+    my $attachment = delete $args{Attachment};
+    my $ticket     = delete $args{Ticket};
+
+    if ( not RT->Config->Get('GnuPG')->{'Enable'} ) {
+        $args{Sign} = $args{Encrypt} = 0;
+        return wantarray ? %args : 0;
+    }
+
+    for my $argument ( qw(Sign Encrypt) ) {
+        next if defined $args{ $argument };
+
+        if ( $attachment and defined $attachment->GetHeader("X-RT-$argument") ) {
+            $args{$argument} = $attachment->GetHeader("X-RT-$argument");
+        } elsif ( $ticket and $argument eq "Encrypt" ) {
+            $args{Encrypt} = $ticket->QueueObj->Encrypt();
+        } elsif ( $ticket and $argument eq "Sign" ) {
+            # Note that $queue->Sign is UI-only, and that all
+            # UI-generated messages explicitly set the X-RT-Crypt header
+            # to 0 or 1; thus this path is only taken for messages
+            # generated _not_ via the web UI.
+            $args{Sign} = $ticket->QueueObj->SignAuto();
+        }
+    }
+
+    return wantarray ? %args : ($args{Sign} || $args{Encrypt});
+}
+
 sub SendEmail {
     my (%args) = (
         Entity => undef,
 sub SendEmail {
     my (%args) = (
         Entity => undef,
@@ -343,6 +383,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' )
     {
@@ -350,37 +395,27 @@ sub SendEmail {
     }
 
     if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
     }
 
     if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
-        my %crypt;
-
-        my $attachment;
-        $attachment = $TransactionObj->Attachments->First
-            if $TransactionObj;
-
-        foreach my $argument ( qw(Sign Encrypt) ) {
-            next if defined $args{ $argument };
-
-            if ( $attachment && defined $attachment->GetHeader("X-RT-$argument") ) {
-                $crypt{$argument} = $attachment->GetHeader("X-RT-$argument");
-            } elsif ( $TicketObj ) {
-                $crypt{$argument} = $TicketObj->QueueObj->$argument();
-            }
-        }
-
-        my $res = SignEncrypt( %args, %crypt );
+        %args = WillSignEncrypt(
+            %args,
+            Attachment => $TransactionObj ? $TransactionObj->Attachments->First : undef,
+            Ticket     => $TicketObj,
+        );
+        my $res = SignEncrypt( %args );
         return $res unless $res > 0;
     }
 
     unless ( $args{'Entity'}->head->get('Date') ) {
         require RT::Date;
         return $res unless $res > 0;
     }
 
     unless ( $args{'Entity'}->head->get('Date') ) {
         require RT::Date;
-        my $date = RT::Date->new( $RT::SystemUser );
+        my $date = RT::Date->new( RT->SystemUser );
         $date->SetToNow;
         $args{'Entity'}->head->set( 'Date', $date->RFC2822( Timezone => 'server' ) );
     }
 
     my $mail_command = RT->Config->Get('MailCommand');
 
         $date->SetToNow;
         $args{'Entity'}->head->set( 'Date', $date->RFC2822( Timezone => 'server' ) );
     }
 
     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;
@@ -388,10 +423,12 @@ sub SendEmail {
 
     if ( $mail_command eq 'sendmailpipe' ) {
         my $path = RT->Config->Get('SendmailPath');
 
     if ( $mail_command eq 'sendmailpipe' ) {
         my $path = RT->Config->Get('SendmailPath');
-        my $args = RT->Config->Get('SendmailArguments');
+        my @args = shellwords(RT->Config->Get('SendmailArguments'));
 
 
-        # SetOutgoingMailFrom
-        if ( RT->Config->Get('SetOutgoingMailFrom') ) {
+        # SetOutgoingMailFrom and bounces conflict, since they both want -f
+        if ( $args{'Bounce'} ) {
+            push @args, shellwords(RT->Config->Get('SendmailBounceArguments'));
+        } elsif ( RT->Config->Get('SetOutgoingMailFrom') ) {
             my $OutgoingMailAddress;
 
             if ($TicketObj) {
             my $OutgoingMailAddress;
 
             if ($TicketObj) {
@@ -407,13 +444,10 @@ sub SendEmail {
 
             $OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
 
 
             $OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
 
-            $args .= " -f $OutgoingMailAddress"
+            push @args, "-f", $OutgoingMailAddress
                 if $OutgoingMailAddress;
         }
 
                 if $OutgoingMailAddress;
         }
 
-        # Set Bounce Arguments
-        $args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
-
         # VERP
         if ( $TransactionObj and
              my $prefix = RT->Config->Get('VERPPrefix') and
         # VERP
         if ( $TransactionObj and
              my $prefix = RT->Config->Get('VERPPrefix') and
@@ -422,30 +456,39 @@ sub SendEmail {
             my $from = $TransactionObj->CreatorObj->EmailAddress;
             $from =~ s/@/=/g;
             $from =~ s/\s//g;
             my $from = $TransactionObj->CreatorObj->EmailAddress;
             $from =~ s/@/=/g;
             $from =~ s/\s//g;
-            $args .= " -f $prefix$from\@$domain";
+            push @args, "-f", "$prefix$from\@$domain";
         }
 
         eval {
             # don't ignore CHLD signal to get proper exit code
             local $SIG{'CHLD'} = 'DEFAULT';
 
         }
 
         eval {
             # 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: $!";
-
             # 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" };
+
+            require IPC::Open2;
+            my ($mail, $stdout);
+            my $pid = IPC::Open2::open2( $stdout, $mail, $path, @args )
+                or die "couldn't execute program: $!";
+
             $args{'Entity'}->print($mail);
             $args{'Entity'}->print($mail);
+            close $mail or die "close pipe failed: $!";
 
 
-            unless ( close $mail ) {
-                die "close pipe failed: $!" if $!; # system error
+            waitpid($pid, 0);
+            if ($?) {
                 # sendmail exit statuses mostly errors with data not software
                 # TODO: status parsing: core dump, exit on signal or EX_*
                 # sendmail exit statuses mostly errors with data not software
                 # TODO: status parsing: core dump, exit on signal or EX_*
-                my $msg = "$msgid: `$path $args` exitted with code ". ($?>>8);
+                my $msg = "$msgid: `$path @args` exited with code ". ($?>>8);
                 $msg = ", interrupted by signal ". ($?&127) if $?&127;
                 $RT::Logger->error( $msg );
                 $msg = ", interrupted by signal ". ($?&127) if $?&127;
                 $RT::Logger->error( $msg );
+                die $msg;
             }
         };
         if ( $@ ) {
             }
         };
         if ( $@ ) {
-            $RT::Logger->crit( "$msgid: Could not send mail with command `$path $args`: " . $@ );
+            $RT::Logger->crit( "$msgid: Could not send mail with command `$path @args`: " . $@ );
+            if ( $TicketObj ) {
+                _RecordSendEmailFailure( $TicketObj );
+            }
             return 0;
         }
     }
             return 0;
         }
     }
@@ -457,6 +500,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 +531,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 +551,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;
         }
     }
@@ -525,7 +577,7 @@ sub PrepareEmailUsingTemplate {
         @_
     );
 
         @_
     );
 
-    my $template = RT::Template->new( $RT::SystemUser );
+    my $template = RT::Template->new( RT->SystemUser );
     $template->LoadGlobalTemplate( $args{'Template'} );
     unless ( $template->id ) {
         return (undef, "Couldn't load template '". $args{'Template'} ."'");
     $template->LoadGlobalTemplate( $args{'Template'} );
     unless ( $template->id ) {
         return (undef, "Couldn't load template '". $args{'Template'} ."'");
@@ -553,6 +605,7 @@ sub SendEmailUsingTemplate {
         Bcc => undef,
         From => RT->Config->Get('CorrespondAddress'),
         InReplyTo => undef,
         Bcc => undef,
         From => RT->Config->Get('CorrespondAddress'),
         InReplyTo => undef,
+        ExtraHeaders => {},
         @_
     );
 
         @_
     );
 
@@ -565,9 +618,12 @@ 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);
 
         foreach grep defined $args{$_}, qw(To Cc Bcc From);
 
+    $mail->head->set( $_ => $args{ExtraHeaders}{$_} )
+        foreach keys %{ $args{ExtraHeaders} };
+
     SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
 
     return SendEmail( Entity => $mail );
     SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
 
     return SendEmail( Entity => $mail );
@@ -585,7 +641,19 @@ sub ForwardTransaction {
 
     my $entity = $txn->ContentAsMIME;
 
 
     my $entity = $txn->ContentAsMIME;
 
-    return SendForward( %args, Entity => $entity, Transaction => $txn );
+    my ( $ret, $msg ) = SendForward( %args, Entity => $entity, Transaction => $txn );
+    if ($ret) {
+        my $ticket = $txn->TicketObj;
+        my ( $ret, $msg ) = $ticket->_NewTransaction(
+            Type  => 'Forward Transaction',
+            Field => $txn->id,
+            Data  => join ', ', grep { length } $args{To}, $args{Cc}, $args{Bcc},
+        );
+        unless ($ret) {
+            $RT::Logger->error("Failed to create transaction: $msg");
+        }
+    }
+    return ( $ret, $msg );
 }
 
 =head2 ForwardTicket TICKET, To => '', Cc => '', Bcc => ''
 }
 
 =head2 ForwardTicket TICKET, To => '', Cc => '', Bcc => ''
@@ -605,13 +673,33 @@ sub ForwardTicket {
     ) for qw(Create Correspond);
 
     my $entity = MIME::Entity->build(
     ) for qw(Create Correspond);
 
     my $entity = MIME::Entity->build(
-        Type => 'multipart/mixed',
+        Type        => 'multipart/mixed',
+        Description => 'forwarded ticket',
     );
     $entity->add_part( $_ ) foreach 
         map $_->ContentAsMIME,
         @{ $txns->ItemsArrayRef };
 
     );
     $entity->add_part( $_ ) foreach 
         map $_->ContentAsMIME,
         @{ $txns->ItemsArrayRef };
 
-    return SendForward( %args, Entity => $entity, Ticket => $ticket, Template => 'Forward Ticket' );
+    my ( $ret, $msg ) = SendForward(
+        %args,
+        Entity   => $entity,
+        Ticket   => $ticket,
+        Template => 'Forward Ticket',
+    );
+
+    if ($ret) {
+        my ( $ret, $msg ) = $ticket->_NewTransaction(
+            Type  => 'Forward Ticket',
+            Field => $ticket->id,
+            Data  => join ', ', grep { length } $args{To}, $args{Cc}, $args{Bcc},
+        );
+        unless ($ret) {
+            $RT::Logger->error("Failed to create transaction: $msg");
+        }
+    }
+
+    return ( $ret, $msg );
+
 }
 
 =head2 SendForward Entity => undef, Ticket => undef, Transaction => undef, Template => undef, To => '', Cc => '', Bcc => ''
 }
 
 =head2 SendForward Entity => undef, Ticket => undef, Transaction => undef, Template => undef, To => '', Cc => '', Bcc => ''
@@ -674,34 +762,55 @@ sub SendForward {
     $mail->head->set( $_ => EncodeToMIME( String => $args{$_} ) )
         foreach grep defined $args{$_}, qw(To Cc Bcc);
 
     $mail->head->set( $_ => EncodeToMIME( String => $args{$_} ) )
         foreach grep defined $args{$_}, qw(To Cc Bcc);
 
-    $mail->attach(
-        Type => 'message/rfc822',
-        Disposition => 'attachment',
-        Description => 'forwarded message',
-        Data => $entity->as_string,
-    );
+    $mail->make_multipart unless $mail->is_multipart;
+    $mail->add_part( $entity );
 
     my $from;
 
     my $from;
-    my $subject = '';
-    $subject = $txn->Subject if $txn;
-    $subject ||= $ticket->Subject if $ticket;
-    if ( RT->Config->Get('ForwardFromUser') ) {
-        $from = ($txn || $ticket)->CurrentUser->UserObj->EmailAddress;
-    } else {
-        # XXX: what if want to forward txn of other object than ticket?
-        $subject = AddSubjectTag( $subject, $ticket );
-        $from = $ticket->QueueObj->CorrespondAddress
-            || RT->Config->Get('CorrespondAddress');
+    unless (defined $mail->head->get('Subject')) {
+        my $subject = '';
+        $subject = $txn->Subject if $txn;
+        $subject ||= $ticket->Subject if $ticket;
+
+        unless ( RT->Config->Get('ForwardFromUser') ) {
+            # XXX: what if want to forward txn of other object than ticket?
+            $subject = AddSubjectTag( $subject, $ticket );
+        }
+
+        $mail->head->set( Subject => EncodeToMIME( String => "Fwd: $subject" ) );
     }
     }
-    $mail->head->set( Subject => EncodeToMIME( String => "Fwd: $subject" ) );
-    $mail->head->set( From    => EncodeToMIME( String => $from ) );
+
+    $mail->head->set(
+        From => EncodeToMIME(
+            String => GetForwardFrom( Transaction => $txn, Ticket => $ticket )
+        )
+    );
 
     my $status = RT->Config->Get('ForwardFromUser')
         # never sign if we forward from User
         ? SendEmail( %args, Entity => $mail, Sign => 0 )
         : SendEmail( %args, Entity => $mail );
     return (0, $ticket->loc("Couldn't send email")) unless $status;
 
     my $status = RT->Config->Get('ForwardFromUser')
         # never sign if we forward from User
         ? SendEmail( %args, Entity => $mail, Sign => 0 )
         : SendEmail( %args, Entity => $mail );
     return (0, $ticket->loc("Couldn't send email")) unless $status;
-    return (1, $ticket->loc("Send email successfully"));
+    return (1, $ticket->loc("Sent email successfully"));
+}
+
+=head2 GetForwardFrom Ticket => undef, Transaction => undef
+
+Resolve the From field to use in forward mail
+
+=cut
+
+sub GetForwardFrom {
+    my %args   = ( Ticket => undef, Transaction => undef, @_ );
+    my $txn    = $args{Transaction};
+    my $ticket = $args{Ticket} || $txn->Object;
+
+    if ( RT->Config->Get('ForwardFromUser') ) {
+        return ( $txn || $ticket )->CurrentUser->EmailAddress;
+    }
+    else {
+        return $ticket->QueueObj->CorrespondAddress
+          || RT->Config->Get('CorrespondAddress');
+    }
 }
 
 =head2 SignEncrypt Entity => undef, Sign => 0, Encrypt => 0
 }
 
 =head2 SignEncrypt Entity => undef, Sign => 0, Encrypt => 0
@@ -861,7 +970,7 @@ sub EncodeToMIME {
         return ($value);
     }
 
         return ($value);
     }
 
-    return ($value) unless $value =~ /[^\x20-\x7e]/;
+    return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
 
     $value =~ s/\s+$//;
 
 
     $value =~ s/\s+$//;
 
@@ -890,7 +999,7 @@ sub EncodeToMIME {
 sub CreateUser {
     my ( $Username, $Address, $Name, $ErrorsTo, $entity ) = @_;
 
 sub CreateUser {
     my ( $Username, $Address, $Name, $ErrorsTo, $entity ) = @_;
 
-    my $NewUser = RT::User->new( $RT::SystemUser );
+    my $NewUser = RT::User->new( RT->SystemUser );
 
     my ( $Val, $Message ) = $NewUser->Create(
         Name => ( $Username || $Address ),
 
     my ( $Val, $Message ) = $NewUser->Create(
         Name => ( $Username || $Address ),
@@ -925,7 +1034,7 @@ sub CreateUser {
     }
 
     #Load the new user object
     }
 
     #Load the new user object
-    my $CurrentUser = new RT::CurrentUser;
+    my $CurrentUser = RT::CurrentUser->new;
     $CurrentUser->LoadByEmail( $Address );
 
     unless ( $CurrentUser->id ) {
     $CurrentUser->LoadByEmail( $Address );
 
     unless ( $CurrentUser->id ) {
@@ -963,25 +1072,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
 
@@ -1050,12 +1166,7 @@ sub ParseAddressFromHeader {
         return ( undef, undef );
     }
 
         return ( undef, undef );
     }
 
-    my $Name = ( $AddrObj->name || $AddrObj->phrase || $AddrObj->comment || $AddrObj->address );
-
-    #Lets take the from and load a user object.
-    my $Address = $AddrObj->address;
-
-    return ( $Address, $Name );
+    return ( $AddrObj->address, $AddrObj->phrase );
 }
 
 =head2 DeleteRecipientsFromHead HEAD RECIPIENTS
 }
 
 =head2 DeleteRecipientsFromHead HEAD RECIPIENTS
@@ -1128,8 +1239,16 @@ sub SetInReplyTo {
         if @references > 10;
 
     my $mail = $args{'Message'};
         if @references > 10;
 
     my $mail = $args{'Message'};
-    $mail->head->set( 'In-Reply-To' => join ' ', @rtid? (@rtid) : (@id) ) if @id || @rtid;
-    $mail->head->set( 'References' => join ' ', @references );
+    $mail->head->set( 'In-Reply-To' => Encode::encode_utf8(join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
+    $mail->head->set( 'References' => Encode::encode_utf8(join ' ', @references) );
+}
+
+sub ExtractTicketId {
+    my $entity = shift;
+
+    my $subject = $entity->head->get('Subject') || '';
+    chomp $subject;
+    return ParseTicketId( $subject );
 }
 
 sub ParseTicketId {
 }
 
 sub ParseTicketId {
@@ -1158,7 +1277,7 @@ sub AddSubjectTag {
     my $subject = shift;
     my $ticket  = shift;
     unless ( ref $ticket ) {
     my $subject = shift;
     my $ticket  = shift;
     unless ( ref $ticket ) {
-        my $tmp = RT::Ticket->new( $RT::SystemUser );
+        my $tmp = RT::Ticket->new( RT->SystemUser );
         $tmp->Load( $ticket );
         $ticket = $tmp;
     }
         $tmp->Load( $ticket );
         $ticket = $tmp;
     }
@@ -1174,7 +1293,7 @@ sub AddSubjectTag {
     }
     return $subject if $subject =~ /\[$tag_re\s+#$id\]/;
 
     }
     return $subject if $subject =~ /\[$tag_re\s+#$id\]/;
 
-    $subject =~ s/(\r\n|\n|\s)/ /gi;
+    $subject =~ s/(\r\n|\n|\s)/ /g;
     chomp $subject;
     return "[". ($queue_tag || RT->Config->Get('rtname')) ." #$id] $subject";
 }
     chomp $subject;
     return "[". ($queue_tag || RT->Config->Get('rtname')) ." #$id] $subject";
 }
@@ -1226,7 +1345,7 @@ sub _LoadPlugins {
         } elsif ( !ref $plugin ) {
             my $Class = $plugin;
             $Class = "RT::Interface::Email::" . $Class
         } elsif ( !ref $plugin ) {
             my $Class = $plugin;
             $Class = "RT::Interface::Email::" . $Class
-                unless $Class =~ /^RT::Interface::Email::/;
+                unless $Class =~ /^RT::/;
             $Class->require or
                 do { $RT::Logger->error("Couldn't load $Class: $@"); next };
 
             $Class->require or
                 do { $RT::Logger->error("Couldn't load $Class: $@"); next };
 
@@ -1338,7 +1457,7 @@ sub Gateway {
     my $Subject = $head->get('Subject') || '';
     chomp $Subject;
     
     my $Subject = $head->get('Subject') || '';
     chomp $Subject;
     
-    # {{{ Lets check for mail loops of various sorts.
+    # Lets check for mail loops of various sorts.
     my ($should_store_machine_generated_message, $IsALoop, $result);
     ( $should_store_machine_generated_message, $ErrorsTo, $result, $IsALoop ) =
       _HandleMachineGeneratedMail(
     my ($should_store_machine_generated_message, $IsALoop, $result);
     ( $should_store_machine_generated_message, $ErrorsTo, $result, $IsALoop ) =
       _HandleMachineGeneratedMail(
@@ -1355,9 +1474,9 @@ sub Gateway {
     }
     # }}}
 
     }
     # }}}
 
-    $args{'ticket'} ||= ParseTicketId( $Subject );
+    $args{'ticket'} ||= ExtractTicketId( $Message );
 
 
-    $SystemTicket = RT::Ticket->new( $RT::SystemUser );
+    $SystemTicket = RT::Ticket->new( RT->SystemUser );
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
     if ( $SystemTicket->id ) {
         $Right = 'ReplyToTicket';
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
     if ( $SystemTicket->id ) {
         $Right = 'ReplyToTicket';
@@ -1366,7 +1485,7 @@ sub Gateway {
     }
 
     #Set up a queue object
     }
 
     #Set up a queue object
-    my $SystemQueueObj = RT::Queue->new( $RT::SystemUser );
+    my $SystemQueueObj = RT::Queue->new( RT->SystemUser );
     $SystemQueueObj->Load( $args{'queue'} );
 
     # We can safely have no queue of we have a known-good ticket
     $SystemQueueObj->Load( $args{'queue'} );
 
     # We can safely have no queue of we have a known-good ticket
@@ -1383,7 +1502,7 @@ sub Gateway {
         SystemQueue   => $SystemQueueObj,
     );
 
         SystemQueue   => $SystemQueueObj,
     );
 
-    # {{{ If authentication fails and no new user was created, get out.
+    # If authentication fails and no new user was created, get out.
     if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
 
         # If the plugins refused to create one, they lose.
     if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
 
         # If the plugins refused to create one, they lose.
@@ -1611,17 +1730,20 @@ sub _RunUnsafeAction {
             return ( 0, "Ticket not taken" );
         }
     } elsif ( $args{'Action'} =~ /^resolve$/i ) {
             return ( 0, "Ticket not taken" );
         }
     } elsif ( $args{'Action'} =~ /^resolve$/i ) {
-        my ( $status, $msg ) = $args{'Ticket'}->SetStatus('resolved');
-        unless ($status) {
+        my $new_status = $args{'Ticket'}->FirstInactiveStatus;
+        if ($new_status) {
+            my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status);
+            unless ($status) {
 
 
-            #Warn the sender that we couldn't actually submit the comment.
-            MailError(
-                To          => $args{'ErrorsTo'},
-                Subject     => "Ticket not resolved",
-                Explanation => $msg,
-                MIMEObj     => $args{'Message'}
-            );
-            return ( 0, "Ticket not resolved" );
+                #Warn the sender that we couldn't actually submit the comment.
+                MailError(
+                    To          => $args{'ErrorsTo'},
+                    Subject     => "Ticket not resolved",
+                    Explanation => $msg,
+                    MIMEObj     => $args{'Message'}
+                );
+                return ( 0, "Ticket not resolved" );
+            }
         }
     } else {
         return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} );
         }
     } else {
         return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} );
@@ -1735,7 +1857,7 @@ sub _HandleMachineGeneratedMail {
     # Squelch replies if necessary
     # Don't let the user stuff the RT-Squelch-Replies-To header.
     if ( $head->get('RT-Squelch-Replies-To') ) {
     # Squelch replies if necessary
     # Don't let the user stuff the RT-Squelch-Replies-To header.
     if ( $head->get('RT-Squelch-Replies-To') ) {
-        $head->add(
+        $head->replace(
             'RT-Relocated-Squelch-Replies-To',
             $head->get('RT-Squelch-Replies-To')
         );
             'RT-Relocated-Squelch-Replies-To',
             $head->get('RT-Squelch-Replies-To')
         );
@@ -1750,8 +1872,8 @@ sub _HandleMachineGeneratedMail {
         # to the scrip. We might want to notify nobody. Or just
         # the RT Owner. Or maybe all Privileged watchers.
         my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
         # to the scrip. We might want to notify nobody. Or just
         # the RT Owner. Or maybe all Privileged watchers.
         my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
-        $head->add( 'RT-Squelch-Replies-To',    $Sender );
-        $head->add( 'RT-DetectedAutoGenerated', 'true' );
+        $head->replace( 'RT-Squelch-Replies-To',    $Sender );
+        $head->replace( 'RT-DetectedAutoGenerated', 'true' );
     }
     return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
 }
     }
     return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
 }
@@ -1772,9 +1894,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;