import rt 3.6.6
[freeside.git] / rt / lib / RT / Interface / Email.pm
index de125f5..dfbd4ae 100755 (executable)
@@ -1,38 +1,40 @@
 # BEGIN BPS TAGGED BLOCK {{{
-#
+# 
 # COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
 #                                          <jesse@bestpractical.com>
-#
+# 
 # (Except where explicitly superseded by other copyright notices)
-#
-#
+# 
+# 
 # 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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+# 
+# 
 # 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.)
-#
+# 
 # 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
@@ -41,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.
-#
+# 
 # END BPS TAGGED BLOCK }}}
 package RT::Interface::Email;
 
@@ -219,7 +221,7 @@ Returns the same array with any IsRTAddress()es weeded out.
 =cut
 
 sub CullRTAddresses {
-    return ( grep { IsRTAddress($_) } @_ );
+    return grep !IsRTAddress($_), @_;
 }
 
 # }}}
@@ -242,14 +244,16 @@ sub MailError {
         level   => $args{'LogLevel'},
         message => $args{'Explanation'}
     );
+    # 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',
-        'X-RT-Loop-Prevention' => $RT::rtname,
+        'Precedence:'             => 'bulk',
+        'X-RT-Loop-Prevention:' => $RT::rtname,
+        'In-Reply-To:'          => $args{'MIMEObj'} ? $args{'MIMEObj'}->head->get('Message-Id') : undef
     );
 
     $entity->attach( Data => $args{'Explanation'} . "\n" );
@@ -297,7 +301,6 @@ sub CreateUser {
     unless ($Val) {
 
         # Deal with the race condition of two account creations at once
-        #
         if ($Username) {
             $NewUser->LoadByName($Username);
         }
@@ -393,12 +396,15 @@ sub ParseSenderAddressFromHead {
     my $head = shift;
 
     #Figure out who's sending this message.
-    my $From = $head->get('Reply-To')
-        || $head->get('From')
-        || $head->get('Sender');
-    return ( ParseAddressFromHeader($From) );
-}
+    foreach my $header ('Reply-To', 'From', 'Sender') {
+        my $From = $head->get($header);
+        my ($addr, $name) = ParseAddressFromHeader($From);
+        # only return if the address is not empty
+        return ($addr, $name) if $addr;
+    }
 
+    return (undef, undef);
+}
 # }}}
 
 # {{{ ParseErrorsToAdddressFromHead
@@ -442,11 +448,12 @@ Takes an address from $head->get('Line') and returns a tuple: user@host, friendl
 sub ParseAddressFromHeader {
     my $Addr = shift;
 
+    # Some broken mailers send:  ""Vincent, Jesse"" <jesse@fsck.com>. Hate
+    $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;                                                                                                                                                  
     my @Addresses = Mail::Address->parse($Addr);
 
-    my $AddrObj = $Addresses[0];
-
-    unless ( ref($AddrObj) ) {
+    my ($AddrObj) = grep ref $_, @Addresses;
+    unless ( $AddrObj ) {
         return ( undef, undef );
     }
 
@@ -568,6 +575,22 @@ sub Gateway {
     #Pull apart the subject line
     my $Subject = $head->get('Subject') || '';
     chomp $Subject;
+    
+    # {{{ 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(
+        Message  => $Message,
+        ErrorsTo => $ErrorsTo,
+        Subject  => $Subject,
+        MessageId => $MessageId
+    );
+
+    # Do not pass loop messages to MailPlugins, to make sure the loop
+    # is broken, unless $RT::StoreLoops is set.
+    if ($IsALoop && !$should_store_machine_generated_message) {
+        return ( 0, $result, undef );
+    }
 
     $args{'ticket'} ||= ParseTicketId($Subject);
 
@@ -643,6 +666,10 @@ sub Gateway {
             $skip_action{$action}++ if $AuthStat == -2;
         }
 
+        # strip actions we should skip
+        @actions = grep !$skip_action{$_}, @actions if $AuthStat == -2;
+        last unless @actions;
+
         last if $AuthStat == -1;
     }
     # {{{ If authentication fails and no new user was created, get out.
@@ -679,22 +706,11 @@ sub Gateway {
         );
     }
 
-    # {{{ Lets check for mail loops of various sorts.
-    my ($continue, $result);
-     ( $continue, $ErrorsTo, $result ) = _HandleMachineGeneratedMail(
-        Message  => $Message,
-        ErrorsTo => $ErrorsTo,
-        Subject  => $Subject,
-        MessageId => $MessageId
-    );
 
-    unless ($continue) {
+    unless ($should_store_machine_generated_message) {
         return ( 0, $result, undef );
     }
     
-    # strip actions we should skip
-    @actions = grep !$skip_action{$_}, @actions;
-
     # if plugin's updated SystemTicket then update arguments
     $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
 
@@ -724,7 +740,7 @@ sub Gateway {
         if ( $id == 0 ) {
             MailError(
                 To          => $ErrorsTo,
-                Subject     => "Ticket creation failed",
+                Subject     => "Ticket creation failed: $Subject",
                 Explanation => $ErrStr,
                 MIMEObj     => $Message
             );
@@ -736,20 +752,23 @@ sub Gateway {
         @actions = grep !/^(comment|correspond)$/, @actions;
         $args{'ticket'} = $id;
 
-    } else {
+    } elsif ( $args{'ticket'} ) {
 
         $Ticket->Load( $args{'ticket'} );
         unless ( $Ticket->Id ) {
             my $error = "Could not find a ticket with id " . $args{'ticket'};
             MailError(
                 To          => $ErrorsTo,
-                Subject     => "Message not recorded",
+                Subject     => "Message not recorded: $Subject",
                 Explanation => $error,
                 MIMEObj     => $Message
             );
 
             return ( 0, $error );
         }
+        $args{'ticket'} = $Ticket->id;
+    } else {
+        return ( 1, "Success", $Ticket );
     }
 
     # }}}
@@ -764,7 +783,7 @@ sub Gateway {
                 #Warn the sender that we couldn't actually submit the comment.
                 MailError(
                     To          => $ErrorsTo,
-                    Subject     => "Message not recorded",
+                    Subject     => "Message not recorded: $Subject",
                     Explanation => $msg,
                     MIMEObj     => $Message
                 );
@@ -856,6 +875,7 @@ EOT
     );
 
     # Also notify the requestor that his request has been dropped.
+    if ($args{'Requestor'} ne $RT::OwnerEmail) {
     MailError(
         To          => $args{'Requestor'},
         Subject     => "Could not load a valid user",
@@ -867,6 +887,7 @@ EOT
         MIMEObj  => $args{'Message'},
         LogLevel => 'error'
     );
+    }
 }
 
 =head2 _HandleMachineGeneratedMail
@@ -877,7 +898,8 @@ Takes named params:
     Subject
 
 Checks the message to see if it's a bounce, if it looks like a loop, if it's autogenerated, etc.
-Returns a triple of ("Should we continue (boolean)", "New value for $ErrorsTo", "Status message");
+Returns a triple of ("Should we continue (boolean)", "New value for $ErrorsTo", "Status message",
+"This message appears to be a loop (boolean)" );
 
 =cut
 
@@ -905,7 +927,7 @@ sub _HandleMachineGeneratedMail {
 
     # Warn someone if it's a loop, before we drop it on the ground
     if ($IsALoop) {
-        $RT::Logger->crit("RT Recieved mail (".$args{MessageId}.") from itself.");
+        $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
 
         #Should we mail it to RTOwner?
         if ($RT::LoopsToRTOwner) {
@@ -918,7 +940,7 @@ sub _HandleMachineGeneratedMail {
         }
 
         #Do we actually want to store it?
-        return ( 0, $ErrorsTo, "Message Bounced" ) unless ($RT::StoreLoops);
+        return ( 0, $ErrorsTo, "Message Bounced", $IsALoop ) unless ($RT::StoreLoops);
     }
 
     # Squelch replies if necessary
@@ -942,7 +964,7 @@ sub _HandleMachineGeneratedMail {
         $head->add( 'RT-Squelch-Replies-To',    $Sender );
         $head->add( 'RT-DetectedAutoGenerated', 'true' );
     }
-    return ( 1, $ErrorsTo, "Handled machine detection" );
+    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
 }
 
 =head2 IsCorrectAction
@@ -953,7 +975,8 @@ Returns a list of valid actions we've found for this message
 
 sub IsCorrectAction {
     my $action = shift;
-    my @actions = split /-/, $action;
+    my @actions = grep $_, split /-/, $action;
+    return ( 0, '(no value)' ) unless @actions;
     foreach (@actions) {
         return ( 0, $_ ) unless /^(?:comment|correspond|take|resolve)$/;
     }