[Rt-commit] rt branch, 4.4/gateway-refactor, created. rt-4.2.10-153-g140d5c8

Alex Vandiver alexmv at bestpractical.com
Thu Mar 12 19:41:12 EDT 2015


The branch, 4.4/gateway-refactor has been created
        at  140d5c84e757692692b13803b058c74783a45346 (commit)

- Log -----------------------------------------------------------------
commit e0497a21877e5acd6d86602a65e0b7080ea7debc
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Jan 30 16:55:25 2014 -0500

    RT::Interface::Email doesn't need to be an Exporter
    
    There is no reason RT::Interface::Email needs to export methods.

diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
index 5d75727..1ffd261 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -26,6 +26,10 @@ When creating linked tickets from the Links box, Time Worked, Time
 Estimated, and Time Left will no longer be copied.  This simplifies time
 reporting.
 
+=item *
+
+RT::Interface::Email no longer exports functions.
+
 =back
 
 =cut
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 7c36441..f9b84e0 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -61,44 +61,10 @@ use Text::ParseWords qw/shellwords/;
 use RT::Util 'safe_run_child';
 use File::Spec;
 
-BEGIN {
-    use base 'Exporter';
-    use vars qw ( @EXPORT_OK);
-
-    # your exported package globals go here,
-    # as well as any optionally exported functions
-    @EXPORT_OK = qw(
-        &CreateUser
-        &GetMessageContent
-        &CheckForLoops
-        &CheckForSuspiciousSender
-        &CheckForAutoGenerated
-        &CheckForBounce
-        &MailError
-        &ParseCcAddressesFromHead
-        &ParseSenderAddressFromHead
-        &ParseErrorsToAddressFromHead
-        &ParseAddressFromHeader
-        &Gateway);
-
-}
-
 =head1 NAME
 
   RT::Interface::Email - helper functions for parsing email sent to RT
 
-=head1 SYNOPSIS
-
-  use lib "!!RT_LIB_PATH!!";
-  use lib "!!RT_ETC_PATH!!";
-
-  use RT::Interface::Email  qw(Gateway CreateUser);
-
-=head1 DESCRIPTION
-
-
-
-
 =head1 METHODS
 
 =head2 CheckForLoops HEAD
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 2d760c2..d678439 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -51,7 +51,7 @@ package RT::Interface::Email::Auth::MailFrom;
 use strict;
 use warnings;
 
-use RT::Interface::Email qw(ParseSenderAddressFromHead CreateUser);
+use RT::Interface::Email;
 
 # This is what the ordinary, non-enhanced gateway does at the moment.
 
@@ -66,7 +66,7 @@ sub GetCurrentUser {
 
 
     # We don't need to do any external lookups
-    my ( $Address, $Name, @errors ) = ParseSenderAddressFromHead( $args{'Message'}->head );
+    my ( $Address, $Name, @errors ) = RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head );
     $RT::Logger->warning("Failed to parse ".join(', ', @errors))
         if @errors;
 
@@ -175,7 +175,9 @@ sub GetCurrentUser {
         }
     }
 
-    $CurrentUser = CreateUser( undef, $Address, $Name, $Address, $args{'Message'} );
+    $CurrentUser = RT::Interface::Email::CreateUser(
+        undef, $Address, $Name, $Address, $args{'Message'}
+    );
 
     return ( $CurrentUser, 1 );
 }
diff --git a/share/html/REST/1.0/NoAuth/mail-gateway b/share/html/REST/1.0/NoAuth/mail-gateway
index 0f70880..5b45a03 100644
--- a/share/html/REST/1.0/NoAuth/mail-gateway
+++ b/share/html/REST/1.0/NoAuth/mail-gateway
@@ -55,7 +55,7 @@ $ticket => undef
 </%ARGS>
 <%init>
 $m->callback( %ARGS, CallbackName => 'Pre' );
-use RT::Interface::Email ();    # It's an exporter, but we don't care
+use RT::Interface::Email;
 $r->content_type('text/plain; charset=utf-8');
 $m->error_format('text');
 my ( $status, $error, $Ticket ) = RT::Interface::Email::Gateway( \%ARGS );

commit e3399f0d2fb128fb08ff973285d90a5f4077e634
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jan 31 16:15:25 2014 -0500

    Reorder functions to more obviously split sending and receiving mail
    
    There are no code changes here, merely reordering of functions and their
    POD.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index f9b84e0..9dc8f9f 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -60,6 +60,7 @@ use Mail::Mailer ();
 use Text::ParseWords qw/shellwords/;
 use RT::Util 'safe_run_child';
 use File::Spec;
+use MIME::Words ();
 
 =head1 NAME
 
@@ -67,735 +68,490 @@ use File::Spec;
 
 =head1 METHODS
 
-=head2 CheckForLoops HEAD
-
-Takes a HEAD object of L<MIME::Head> class and returns true if the
-message's been sent by this RT instance. Uses "X-RT-Loop-Prevention"
-field of the head for test.
-
-=cut
-
-sub CheckForLoops {
-    my $head = shift;
-
-    # If this instance of RT sent it our, we don't want to take it in
-    my $RTLoop = Encode::decode( "UTF-8", $head->get("X-RT-Loop-Prevention") || "" );
-    chomp ($RTLoop); # remove that newline
-    if ( $RTLoop eq RT->Config->Get('rtname') ) {
-        return 1;
-    }
-
-    # TODO: We might not trap the case where RT instance A sends a mail
-    # to RT instance B which sends a mail to ...
-    return undef;
-}
-
-=head2 CheckForSuspiciousSender HEAD
+=head2 Gateway ARGSREF
 
-Takes a HEAD object of L<MIME::Head> class and returns true if sender
-is suspicious. Suspicious means mailer daemon.
+Takes parameters:
 
-See also L</ParseSenderAddressFromHead>.
+    action
+    queue
+    message
 
-=cut
 
-sub CheckForSuspiciousSender {
-    my $head = shift;
+This performs all the "guts" of the mail rt-mailgate program, and is
+designed to be called from the web interface with a message, user
+object, and so on.
 
-    #if it's from a postmaster or mailer daemon, it's likely a bounce.
+Can also take an optional 'ticket' parameter; this ticket id overrides
+any ticket id found in the subject.
 
-    #TODO: better algorithms needed here - there is no standards for
-    #bounces, so it's very difficult to separate them from anything
-    #else.  At the other hand, the Return-To address is only ment to be
-    #used as an error channel, we might want to put up a separate
-    #Return-To address which is treated differently.
+Returns:
 
-    #TODO: search through the whole email and find the right Ticket ID.
+    An array of:
 
-    my ( $From, $junk ) = ParseSenderAddressFromHead($head);
+    (status code, message, optional ticket object)
 
-    # If unparseable (non-ASCII), $From can come back undef
-    return undef if not defined $From;
+    status code is a numeric value.
 
-    if (   ( $From =~ /^mailer-daemon\@/i )
-        or ( $From =~ /^postmaster\@/i )
-        or ( $From eq "" ))
-    {
-        return (1);
+      for temporary failures, the status code should be -75
 
-    }
+      for permanent failures which are handled by RT, the status code
+      should be 0
 
-    return undef;
-}
+      for succces, the status code should be 1
 
-=head2 CheckForAutoGenerated HEAD
 
-Takes a HEAD object of L<MIME::Head> class and returns true if message is
-autogenerated. Checks C<Precedence>, C<Auto-Submitted>, and
-C<X-FC-Machinegenerated> fields of the head in tests.
 
 =cut
 
-sub CheckForAutoGenerated {
-    my $head = shift;
-
-    if (grep { /^(bulk|junk)/i } $head->get_all("Precedence")) {
-        return (1);
-    }
+sub Gateway {
+    my $argsref = shift;
+    my %args    = (
+        action  => 'correspond',
+        queue   => '1',
+        ticket  => undef,
+        message => undef,
+        %$argsref
+    );
 
-    # Per RFC3834, any Auto-Submitted header which is not "no" means
-    # it is auto-generated.
-    my $AutoSubmitted = $head->get("Auto-Submitted") || "";
-    if ( length $AutoSubmitted and $AutoSubmitted ne "no" ) {
-        return (1);
-    }
+    my $SystemTicket;
+    my $Right;
 
-    # First Class mailer uses this as a clue.
-    my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
-    if ( $FCJunk =~ /^true/i ) {
-        return (1);
+    # Validate the action
+    my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
+    unless ($status) {
+        return (
+            -75,
+            "Invalid 'action' parameter "
+                . $actions[0]
+                . " for queue "
+                . $args{'queue'},
+            undef
+        );
     }
 
-    return (0);
-}
-
+    my $parser = RT::EmailParser->new();
+    $parser->SmartParseMIMEEntityFromScalar(
+        Message => $args{'message'},
+        Decode => 0,
+        Exact => 1,
+    );
 
-sub CheckForBounce {
-    my $head = shift;
+    my $Message = $parser->Entity();
+    unless ($Message) {
+        MailError(
+            Subject     => "RT Bounce: Unparseable message",
+            Explanation => "RT couldn't process the message below",
+            Attach      => $args{'message'}
+        );
 
-    my $ReturnPath = $head->get("Return-path") || "";
-    return ( $ReturnPath =~ /<>/ );
-}
+        return ( 0,
+            "Failed to parse this message. Something is likely badly wrong with the message"
+        );
+    }
 
+    my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
+    push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
+    @mail_plugins = _LoadPlugins( @mail_plugins );
 
-=head2 MailError PARAM HASH
+    #Set up a queue object
+    my $SystemQueueObj = RT::Queue->new( RT->SystemUser );
+    $SystemQueueObj->Load( $args{'queue'} );
 
-Sends an error message. Takes a param hash:
+    my %skip_plugin;
+    foreach my $class( grep !ref, @mail_plugins ) {
+        # check if we should apply filter before decoding
+        my $check_cb = do {
+            no strict 'refs';
+            *{ $class . "::ApplyBeforeDecode" }{CODE};
+        };
+        next unless defined $check_cb;
+        next unless $check_cb->(
+            Message       => $Message,
+            RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
+        );
 
-=over 4
+        $skip_plugin{ $class }++;
 
-=item From - sender's address, by default is 'CorrespondAddress';
+        my $Code = do {
+            no strict 'refs';
+            *{ $class . "::GetCurrentUser" }{CODE};
+        };
+        my ($status, $msg) = $Code->(
+            Message       => $Message,
+            RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
+        );
+        next if $status > 0;
 
-=item To - recipient, by default is 'OwnerEmail';
+        if ( $status == -2 ) {
+            return (1, $msg, undef);
+        } elsif ( $status == -1 ) {
+            return (0, $msg, undef);
+        }
+    }
+    @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
+    $parser->_DecodeBodies;
+    $parser->RescueOutlook;
+    $parser->_PostProcessNewEntity;
 
-=item Bcc - optional Bcc recipients;
+    my $head = $Message->head;
+    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
+    my $Sender = (ParseSenderAddressFromHead( $head ))[0];
+    my $From = Encode::decode( "UTF-8", $head->get("From") );
+    chomp $From if defined $From;
 
-=item Subject - subject of the message, default is 'There has been an error';
+    my $MessageId = Encode::decode( "UTF-8", $head->get('Message-ID') )
+        || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
 
-=item Explanation - main content of the error, default value is 'Unexplained error';
+    #Pull apart the subject line
+    my $Subject = Encode::decode( "UTF-8", $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
+    );
 
-=item MIMEObj - optional MIME entity that's attached to the error mail, as well we
-add 'In-Reply-To' field to the error that points to this message.
+    # 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 );
+    }
+    # }}}
 
-=item Attach - optional text that attached to the error as 'message/rfc822' part.
+    $args{'ticket'} ||= ExtractTicketId( $Message );
 
-=item LogLevel - log level under which we should write the subject and
-explanation message into the log, by default we log it as critical.
+    # ExtractTicketId may have been overridden, and edited the Subject
+    my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
+    chomp $NewSubject;
 
-=back
+    $SystemTicket = RT::Ticket->new( RT->SystemUser );
+    $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
+    if ( $SystemTicket->id ) {
+        $Right = 'ReplyToTicket';
+    } else {
+        $Right = 'CreateTicket';
+    }
 
-=cut
+    # We can safely have no queue of we have a known-good ticket
+    unless ( $SystemTicket->id || $SystemQueueObj->id ) {
+        return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
+    }
 
-sub MailError {
-    my %args = (
-        To          => RT->Config->Get('OwnerEmail'),
-        Bcc         => undef,
-        From        => RT->Config->Get('CorrespondAddress'),
-        Subject     => 'There has been an error',
-        Explanation => 'Unexplained error',
-        MIMEObj     => undef,
-        Attach      => undef,
-        LogLevel    => 'crit',
-        @_
+    my ($AuthStat, $CurrentUser, $error) = GetAuthenticationLevel(
+        MailPlugins   => \@mail_plugins,
+        Actions       => \@actions,
+        Message       => $Message,
+        RawMessageRef => \$args{message},
+        SystemTicket  => $SystemTicket,
+        SystemQueue   => $SystemQueueObj,
     );
 
-    $RT::Logger->log(
-        level   => $args{'LogLevel'},
-        message => "$args{Subject}: $args{'Explanation'}",
-    ) if $args{'LogLevel'};
+    # If authentication fails and no new user was created, get out.
+    if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
 
-    # the colons are necessary to make ->build include non-standard headers
-    my %entity_args = (
-        Type                    => "multipart/mixed",
-        From                    => Encode::encode( "UTF-8", $args{'From'} ),
-        Bcc                     => Encode::encode( "UTF-8", $args{'Bcc'} ),
-        To                      => Encode::encode( "UTF-8", $args{'To'} ),
-        Subject                 => EncodeToMIME( String => $args{'Subject'} ),
-        'X-RT-Loop-Prevention:' => Encode::encode( "UTF-8", RT->Config->Get('rtname') ),
-    );
+        # If the plugins refused to create one, they lose.
+        unless ( $AuthStat == -1 ) {
+            _NoAuthorizedUserFound(
+                Right     => $Right,
+                Message   => $Message,
+                Requestor => $ErrorsTo,
+                Queue     => $args{'queue'}
+            );
 
-    # only set precedence if the sysadmin wants us to
-    if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) {
-        $entity_args{'Precedence:'} =
-            Encode::encode( "UTF-8", RT->Config->Get('DefaultErrorMailPrecedence') );
+        }
+        return ( 0, "Could not load a valid user", undef );
     }
 
-    my $entity = MIME::Entity->build(%entity_args);
-    SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} );
+    # If we got a user, but they don't have the right to say things
+    if ( $AuthStat == 0 ) {
+        MailError(
+            To          => $ErrorsTo,
+            Subject     => "Permission Denied",
+            Explanation =>
+                "You do not have permission to communicate with RT",
+            MIMEObj => $Message
+        );
+        return (
+            0,
+            ($CurrentUser->EmailAddress || $CurrentUser->Name)
+            . " ($Sender) tried to submit a message to "
+                . $args{'Queue'}
+                . " without permission.",
+            undef
+        );
+    }
 
-    $entity->attach(
-        Type    => "text/plain",
-        Charset => "UTF-8",
-        Data    => Encode::encode( "UTF-8", $args{'Explanation'} . "\n" ),
-    );
 
-    if ( $args{'MIMEObj'} ) {
-        $args{'MIMEObj'}->sync_headers;
-        $entity->add_part( $args{'MIMEObj'} );
+    unless ($should_store_machine_generated_message) {
+        return ( 0, $result, undef );
     }
 
-    if ( $args{'Attach'} ) {
-        $entity->attach( Data => Encode::encode( "UTF-8", $args{'Attach'} ), Type => 'message/rfc822' );
+    $head->replace('X-RT-Interface' => 'Email');
 
-    }
+    # if plugin's updated SystemTicket then update arguments
+    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
 
-    SendEmail( Entity => $entity, Bounce => 1 );
-}
+    my $Ticket = RT::Ticket->new($CurrentUser);
 
+    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
+    {
 
-=head2 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ]
+        my @Cc;
+        my @Requestors = ( $CurrentUser->id );
 
-Sends an email (passed as a L<MIME::Entity> object C<ENTITY>) using
-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.
+        if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
+            @Cc = ParseCcAddressesFromHead(
+                Head        => $head,
+                CurrentUser => $CurrentUser,
+                QueueObj    => $SystemQueueObj
+            );
+        }
 
-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.
+        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
+            Queue     => $SystemQueueObj->Id,
+            Subject   => $NewSubject,
+            Requestor => \@Requestors,
+            Cc        => \@Cc,
+            MIMEObj   => $Message
+        );
+        if ( $id == 0 ) {
+            MailError(
+                To          => $ErrorsTo,
+                Subject     => "Ticket creation failed: $Subject",
+                Explanation => $ErrStr,
+                MIMEObj     => $Message
+            );
+            return ( 0, "Ticket creation From: $From failed: $ErrStr", $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.
+        # strip comments&corresponds from the actions we don't need
+        # to record them if we've created the ticket just now
+        @actions = grep !/^(comment|correspond)$/, @actions;
+        $args{'ticket'} = $id;
 
-Returns 1 on success, 0 on error or -1 if message has no recipients
-and hasn't been sent.
+    } elsif ( $args{'ticket'} ) {
 
-=head3 Signing and Encrypting
+        $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",
+                Explanation => $error,
+                MIMEObj     => $Message
+            );
 
-This function as well signs and/or encrypts the message according to
-headers of a transaction's attachment or properties of a ticket's queue.
-To get full access to the configuration Ticket and/or Transaction
-arguments must be provided, but you can force behaviour using Sign
-and/or Encrypt arguments.
+            return ( 0, $error );
+        }
+        $args{'ticket'} = $Ticket->id;
+    } else {
+        return ( 1, "Success", $Ticket );
+    }
 
-The following precedence of arguments are used to figure out if
-the message should be encrypted and/or signed:
+    # }}}
 
-* if Sign or Encrypt argument is defined then its value is used
+    my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
+    foreach my $action (@actions) {
 
-* else if Transaction's first attachment has X-RT-Sign or X-RT-Encrypt
-header field then it's value is used
+        #   If the action is comment, add a comment.
+        if ( $action =~ /^(?:comment|correspond)$/i ) {
+            my $method = ucfirst lc $action;
+            my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );
+            unless ($status) {
 
-* else properties of a queue of the Ticket are used.
+                #Warn the sender that we couldn't actually submit the comment.
+                MailError(
+                    To          => $ErrorsTo,
+                    Subject     => "Message not recorded ($method): $Subject",
+                    Explanation => $msg,
+                    MIMEObj     => $Message
+                );
+                return ( 0, "Message From: $From not recorded: $msg", $Ticket );
+            }
+        } elsif ($unsafe_actions) {
+            my ( $status, $msg ) = _RunUnsafeAction(
+                Action      => $action,
+                ErrorsTo    => $ErrorsTo,
+                Message     => $Message,
+                Ticket      => $Ticket,
+                CurrentUser => $CurrentUser,
+            );
+            return ($status, $msg, $Ticket) unless $status == 1;
+        }
+    }
+    return ( 1, "Success", $Ticket );
+}
 
-=cut
+=head2 IsCorrectAction
 
-sub WillSignEncrypt {
-    my %args = @_;
-    my $attachment = delete $args{Attachment};
-    my $ticket     = delete $args{Ticket};
+Returns a list of valid actions we've found for this message
 
-    if ( not RT->Config->Get('Crypt')->{'Enable'} ) {
-        $args{Sign} = $args{Encrypt} = 0;
-        return wantarray ? %args : 0;
+=cut
+
+sub IsCorrectAction {
+    my $action = shift;
+    my @actions = grep $_, split /-/, $action;
+    return ( 0, '(no value)' ) unless @actions;
+    foreach ( @actions ) {
+        return ( 0, $_ ) unless /^(?:comment|correspond|take|resolve)$/;
     }
+    return ( 1, @actions );
+}
 
-    for my $argument ( qw(Sign Encrypt) ) {
-        next if defined $args{ $argument };
+sub _LoadPlugins {
+    my @mail_plugins = @_;
 
-        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();
+    my @res;
+    foreach my $plugin (@mail_plugins) {
+        if ( ref($plugin) eq "CODE" ) {
+            push @res, $plugin;
+        } elsif ( !ref $plugin ) {
+            my $Class = $plugin;
+            $Class = "RT::Interface::Email::" . $Class
+                unless $Class =~ /^RT::/;
+            $Class->require or
+                do { $RT::Logger->error("Couldn't load $Class: $@"); next };
+
+            no strict 'refs';
+            unless ( defined *{ $Class . "::GetCurrentUser" }{CODE} ) {
+                $RT::Logger->crit( "No GetCurrentUser code found in $Class module");
+                next;
+            }
+            push @res, $Class;
+        } else {
+            $RT::Logger->crit( "$plugin - is not class name or code reference");
         }
     }
-
-    return wantarray ? %args : ($args{Sign} || $args{Encrypt});
+    return @res;
 }
 
-sub SendEmail {
-    my (%args) = (
-        Entity => undef,
-        Bounce => 0,
-        Ticket => undef,
-        Transaction => undef,
+=head2 GetAuthenticationLevel
+
+    # Authentication Level
+    # -1 - Get out.  this user has been explicitly declined
+    # 0 - User may not do anything (Not used at the moment)
+    # 1 - Normal user
+    # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate
+
+=cut
+
+sub GetAuthenticationLevel {
+    my %args = (
+        MailPlugins   => [],
+        Actions       => [],
+        Message       => undef,
+        RawMessageRef => undef,
+        SystemTicket  => undef,
+        SystemQueue   => undef,
         @_,
     );
 
-    my $TicketObj = $args{'Ticket'};
-    my $TransactionObj = $args{'Transaction'};
+    my ( $CurrentUser, $AuthStat, $error );
 
-    unless ( $args{'Entity'} ) {
-        $RT::Logger->crit( "Could not send mail without 'Entity' object" );
-        return 0;
-    }
+    # Initalize AuthStat so comparisons work correctly
+    $AuthStat = -9999999;
 
-    my $msgid = Encode::decode( "UTF-8", $args{'Entity'}->head->get('Message-ID') || '' );
-    chomp $msgid;
-    
-    # If we don't have any recipients to send to, don't send a message;
-    unless ( $args{'Entity'}->head->get('To')
-        || $args{'Entity'}->head->get('Cc')
-        || $args{'Entity'}->head->get('Bcc') )
-    {
-        $RT::Logger->info( $msgid . " No recipients found. Not sending." );
-        return -1;
-    }
-
-    if ($args{'Entity'}->head->get('X-RT-Squelch')) {
-        $RT::Logger->info( $msgid . " Squelch header found. Not sending." );
-        return -1;
-    }
-
-    if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
-        and !$args{'Entity'}->head->get("Precedence")
-    ) {
-        $args{'Entity'}->head->replace( 'Precedence', Encode::encode("UTF-8",$precedence) );
-    }
-
-    if ( $TransactionObj && !$TicketObj
-        && $TransactionObj->ObjectType eq 'RT::Ticket' )
-    {
-        $TicketObj = $TransactionObj->Object;
-    }
-
-    my $head = $args{'Entity'}->head;
-    unless ( $head->get('Date') ) {
-        require RT::Date;
-        my $date = RT::Date->new( RT->SystemUser );
-        $date->SetToNow;
-        $head->replace( 'Date', Encode::encode("UTF-8",$date->RFC2822( Timezone => 'server' ) ) );
-    }
-    unless ( $head->get('MIME-Version') ) {
-        # We should never have to set the MIME-Version header
-        $head->replace( 'MIME-Version', '1.0' );
-    }
-    unless ( $head->get('Content-Transfer-Encoding') ) {
-        # fsck.com #5959: Since RT sends 8bit mail, we should say so.
-        $head->replace( 'Content-Transfer-Encoding', '8bit' );
-    }
-
-    if ( RT->Config->Get('Crypt')->{'Enable'} ) {
-        %args = WillSignEncrypt(
-            %args,
-            Attachment => $TransactionObj ? $TransactionObj->Attachments->First : undef,
-            Ticket     => $TicketObj,
-        );
-        my $res = SignEncrypt( %args );
-        return $res unless $res > 0;
-    }
-
-    my $mail_command = RT->Config->Get('MailCommand');
-
-    # if it is a sub routine, we just return it;
-    return $mail_command->($args{'Entity'}) if UNIVERSAL::isa( $mail_command, 'CODE' );
-
-    if ( $mail_command eq 'sendmailpipe' ) {
-        my $path = RT->Config->Get('SendmailPath');
-        my @args = shellwords(RT->Config->Get('SendmailArguments'));
-        push @args, "-t" unless grep {$_ eq "-t"} @args;
-
-        # SetOutgoingMailFrom and bounces conflict, since they both want -f
-        if ( $args{'Bounce'} ) {
-            push @args, shellwords(RT->Config->Get('SendmailBounceArguments'));
-        } elsif ( my $MailFrom = RT->Config->Get('SetOutgoingMailFrom') ) {
-            my $OutgoingMailAddress = $MailFrom =~ /\@/ ? $MailFrom : undef;
-            my $Overrides = RT->Config->Get('OverrideOutgoingMailFrom') || {};
-
-            if ($TicketObj) {
-                my $Queue = $TicketObj->QueueObj;
-                my $QueueAddressOverride = $Overrides->{$Queue->id}
-                    || $Overrides->{$Queue->Name};
-
-                if ($QueueAddressOverride) {
-                    $OutgoingMailAddress = $QueueAddressOverride;
-                } else {
-                    $OutgoingMailAddress ||= $Queue->CorrespondAddress
-                        || RT->Config->Get('CorrespondAddress');
-                }
-            }
-            elsif ($Overrides->{'Default'}) {
-                $OutgoingMailAddress = $Overrides->{'Default'};
-            }
-
-            push @args, "-f", $OutgoingMailAddress
-                if $OutgoingMailAddress;
-        }
-
-        # VERP
-        if ( $TransactionObj and
-             my $prefix = RT->Config->Get('VERPPrefix') and
-             my $domain = RT->Config->Get('VERPDomain') )
-        {
-            my $from = $TransactionObj->CreatorObj->EmailAddress;
-            $from =~ s/@/=/g;
-            $from =~ s/\s//g;
-            push @args, "-f", "$prefix$from\@$domain";
-        }
-
-        eval {
-            # don't ignore CHLD signal to get proper exit code
-            local $SIG{'CHLD'} = 'DEFAULT';
-
-            # 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);
-            close $mail or die "close pipe failed: $!";
-
-            waitpid($pid, 0);
-            if ($?) {
-                # sendmail exit statuses mostly errors with data not software
-                # TODO: status parsing: core dump, exit on signal or EX_*
-                my $msg = "$msgid: `$path @args` exited 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 ( $TicketObj ) {
-                _RecordSendEmailFailure( $TicketObj );
-            }
-            return 0;
-        }
-    }
-    else {
-        local ($ENV{'MAILADDRESS'}, $ENV{'PERL_MAILERS'});
+    # if plugin returns AuthStat -2 we skip action
+    # NOTE: this is experimental API and it would be changed
+    my %skip_action = ();
 
-        my @mailer_args = ($mail_command);
-        if ( $mail_command eq 'sendmail' ) {
-            $ENV{'PERL_MAILERS'} = RT->Config->Get('SendmailPath');
-            push @mailer_args, grep {$_ ne "-t"}
-                split(/\s+/, RT->Config->Get('SendmailArguments'));
-        } elsif ( $mail_command eq 'testfile' ) {
-            unless ($Mail::Mailer::testfile::config{outfile}) {
-                $Mail::Mailer::testfile::config{outfile} = File::Temp->new;
-                $RT::Logger->info("Storing outgoing emails in $Mail::Mailer::testfile::config{outfile}");
-            }
+    # Since this needs loading, no matter what
+    foreach (@{ $args{MailPlugins} }) {
+        my ($Code, $NewAuthStat);
+        if ( ref($_) eq "CODE" ) {
+            $Code = $_;
         } else {
-            push @mailer_args, RT->Config->Get('MailParams');
-        }
-
-        unless ( $args{'Entity'}->send( @mailer_args ) ) {
-            $RT::Logger->crit( "$msgid: Could not send mail." );
-            if ( $TicketObj ) {
-                _RecordSendEmailFailure( $TicketObj );
-            }
-            return 0;
+            no strict 'refs';
+            $Code = *{ $_ . "::GetCurrentUser" }{CODE};
         }
-    }
-    return 1;
-}
-
-=head2 PrepareEmailUsingTemplate Template => '', Arguments => {}
-
-Loads a template. Parses it using arguments if it's not empty.
-Returns a tuple (L<RT::Template> object, error message).
 
-Note that even if a template object is returned MIMEObj method
-may return undef for empty templates.
-
-=cut
-
-sub PrepareEmailUsingTemplate {
-    my %args = (
-        Template => '',
-        Arguments => {},
-        @_
-    );
-
-    my $template = RT::Template->new( RT->SystemUser );
-    $template->LoadGlobalTemplate( $args{'Template'} );
-    unless ( $template->id ) {
-        return (undef, "Couldn't load template '". $args{'Template'} ."'");
-    }
-    return $template if $template->IsEmpty;
-
-    my ($status, $msg) = $template->Parse( %{ $args{'Arguments'} } );
-    return (undef, $msg) unless $status;
-
-    return $template;
-}
-
-=head2 SendEmailUsingTemplate Template => '', Arguments => {}, From => CorrespondAddress, To => '', Cc => '', Bcc => ''
-
-Sends email using a template, takes name of template, arguments for it and recipients.
-
-=cut
-
-sub SendEmailUsingTemplate {
-    my %args = (
-        Template => '',
-        Arguments => {},
-        To => undef,
-        Cc => undef,
-        Bcc => undef,
-        From => RT->Config->Get('CorrespondAddress'),
-        InReplyTo => undef,
-        ExtraHeaders => {},
-        @_
-    );
-
-    my ($template, $msg) = PrepareEmailUsingTemplate( %args );
-    return (0, $msg) unless $template;
-
-    my $mail = $template->MIMEObj;
-    unless ( $mail ) {
-        $RT::Logger->info("Message is not sent as template #". $template->id ." is empty");
-        return -1;
-    }
-
-    $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ $_ } ) )
-        foreach grep defined $args{$_}, qw(To Cc Bcc From);
-
-    $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ExtraHeaders}{$_} ) )
-        foreach keys %{ $args{ExtraHeaders} };
-
-    SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
-
-    return SendEmail( Entity => $mail );
-}
-
-=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 GetForwardAttachments Ticket => undef, Transaction => undef
-
-Resolve the Attachments to forward
-
-=cut
-
-sub GetForwardAttachments {
-    my %args   = ( Ticket => undef, Transaction => undef, @_ );
-    my $txn    = $args{Transaction};
-    my $ticket = $args{Ticket} || $txn->Object;
-
-    my $attachments = RT::Attachments->new( $ticket->CurrentUser );
-    if ($txn) {
-        $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
-    }
-    else {
-        $attachments->LimitByTicket( $ticket->id );
-        $attachments->Limit(
-            ALIAS         => $attachments->TransactionAlias,
-            FIELD         => 'Type',
-            OPERATOR      => 'IN',
-            VALUE         => [ qw(Create Correspond) ],
-        );
-    }
-    return $attachments;
-}
-
-
-=head2 SignEncrypt Entity => undef, Sign => 0, Encrypt => 0
-
-Signs and encrypts message using L<RT::Crypt>, but as well handle errors
-with users' keys.
-
-If a recipient has no key or has other problems with it, then the
-unction sends a error to him using 'Error: public key' template.
-Also, notifies RT's owner using template 'Error to RT owner: public key'
-to inform that there are problems with users' keys. Then we filter
-all bad recipients and retry.
-
-Returns 1 on success, 0 on error and -1 if all recipients are bad and
-had been filtered out.
-
-=cut
-
-sub SignEncrypt {
-    my %args = (
-        Entity => undef,
-        Sign => 0,
-        Encrypt => 0,
-        @_
-    );
-    return 1 unless $args{'Sign'} || $args{'Encrypt'};
-
-    my $msgid = Encode::decode( "UTF-8", $args{'Entity'}->head->get('Message-ID') || '' );
-    chomp $msgid;
-
-    $RT::Logger->debug("$msgid Signing message") if $args{'Sign'};
-    $RT::Logger->debug("$msgid Encrypting message") if $args{'Encrypt'};
-
-    my %res = RT::Crypt->SignEncrypt( %args );
-    return 1 unless $res{'exit_code'};
-
-    my @status = RT::Crypt->ParseStatus(
-        Protocol => $res{'Protocol'}, Status => $res{'status'},
-    );
-
-    my @bad_recipients;
-    foreach my $line ( @status ) {
-        # if the passphrase fails, either you have a bad passphrase
-        # or gpg-agent has died.  That should get caught in Create and
-        # Update, but at least throw an error here
-        if (($line->{'Operation'}||'') eq 'PassphraseCheck'
-            && $line->{'Status'} =~ /^(?:BAD|MISSING)$/ ) {
-            $RT::Logger->error( "$line->{'Status'} PASSPHRASE: $line->{'Message'}" );
-            return 0;
-        }
-        next unless ($line->{'Operation'}||'') eq 'RecipientsCheck';
-        next if $line->{'Status'} eq 'DONE';
-        $RT::Logger->error( $line->{'Message'} );
-        push @bad_recipients, $line;
-    }
-    return 0 unless @bad_recipients;
+        foreach my $action (@{ $args{Actions} }) {
+            ( $CurrentUser, $NewAuthStat ) = $Code->(
+                Message       => $args{Message},
+                RawMessageRef => $args{RawMessageRef},
+                CurrentUser   => $CurrentUser,
+                AuthLevel     => $AuthStat,
+                Action        => $action,
+                Ticket        => $args{SystemTicket},
+                Queue         => $args{SystemQueue},
+            );
 
-    $_->{'AddressObj'} = (Email::Address->parse( $_->{'Recipient'} ))[0]
-        foreach @bad_recipients;
+# You get the highest level of authentication you were assigned, unless you get the magic -1
+# If a module returns a "-1" then we discard the ticket, so.
+            $AuthStat = $NewAuthStat
+                if ( $NewAuthStat > $AuthStat or $NewAuthStat == -1 or $NewAuthStat == -2 );
 
-    foreach my $recipient ( @bad_recipients ) {
-        my $status = SendEmailUsingTemplate(
-            To        => $recipient->{'AddressObj'}->address,
-            Template  => 'Error: public key',
-            Arguments => {
-                %$recipient,
-                TicketObj      => $args{'Ticket'},
-                TransactionObj => $args{'Transaction'},
-            },
-        );
-        unless ( $status ) {
-            $RT::Logger->error("Couldn't send 'Error: public key'");
+            last if $AuthStat == -1;
+            $skip_action{$action}++ if $AuthStat == -2;
         }
-    }
-
-    my $status = SendEmailUsingTemplate(
-        To        => RT->Config->Get('OwnerEmail'),
-        Template  => 'Error to RT owner: public key',
-        Arguments => {
-            BadRecipients  => \@bad_recipients,
-            TicketObj      => $args{'Ticket'},
-            TransactionObj => $args{'Transaction'},
-        },
-    );
-    unless ( $status ) {
-        $RT::Logger->error("Couldn't send 'Error to RT owner: public key'");
-    }
 
-    DeleteRecipientsFromHead(
-        $args{'Entity'}->head,
-        map $_->{'AddressObj'}->address, @bad_recipients
-    );
+        # strip actions we should skip
+        @{$args{Actions}} = grep !$skip_action{$_}, @{$args{Actions}}
+            if $AuthStat == -2;
+        last unless @{$args{Actions}};
 
-    unless ( $args{'Entity'}->head->get('To')
-          || $args{'Entity'}->head->get('Cc')
-          || $args{'Entity'}->head->get('Bcc') )
-    {
-        $RT::Logger->debug("$msgid No recipients that have public key, not sending");
-        return -1;
+        last if $AuthStat == -1;
     }
 
-    # redo without broken recipients
-    %res = RT::Crypt->SignEncrypt( %args );
-    return 0 if $res{'exit_code'};
+    return $AuthStat if !wantarray;
 
-    return 1;
+    return ($AuthStat, $CurrentUser, $error);
 }
 
-use MIME::Words ();
-
-=head2 EncodeToMIME
-
-Takes a hash with a String and a Charset. Returns the string encoded
-according to RFC2047, using B (base64 based) encoding.
-
-String must be a perl string, octets are returned.
+=head2 _NoAuthorizedUserFound
 
-If Charset is not provided then $EmailOutputEncoding config option
-is used, or "latin-1" if that is not set.
+Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
 
 =cut
 
-sub EncodeToMIME {
+sub _NoAuthorizedUserFound {
     my %args = (
-        String => undef,
-        Charset  => undef,
+        Right     => undef,
+        Message   => undef,
+        Requestor => undef,
+        Queue     => undef,
         @_
     );
-    my $value = $args{'String'};
-    return $value unless $value; # 0 is perfect ascii
-    my $charset  = $args{'Charset'} || RT->Config->Get('EmailOutputEncoding');
-    my $encoding = 'B';
-
-    # using RFC2047 notation, sec 2.
-    # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
-
-    # An 'encoded-word' may not be more than 75 characters long
-    #
-    # MIME encoding increases 4/3*(number of bytes), and always in multiples
-    # of 4. Thus we have to find the best available value of bytes available
-    # for each chunk.
-    #
-    # First we get the integer max which max*4/3 would fit on space.
-    # Then we find the greater multiple of 3 lower or equal than $max.
-    my $max = int(
-        (   ( 75 - length( '=?' . $charset . '?' . $encoding . '?' . '?=' ) )
-            * 3
-        ) / 4
-    );
-    $max = int( $max / 3 ) * 3;
-
-    chomp $value;
 
-    if ( $max <= 0 ) {
+    # Notify the RT Admin of the failure.
+    MailError(
+        To          => RT->Config->Get('OwnerEmail'),
+        Subject     => "Could not load a valid user",
+        Explanation => <<EOT,
+RT could not load a valid user, and RT's configuration does not allow
+for the creation of a new user for this email (@{[$args{Requestor}]}).
 
-        # gives an error...
-        $RT::Logger->crit("Can't encode! Charset or encoding too big.");
-        return ($value);
-    }
+You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
+queue @{[$args{'Queue'}]}.
 
-    return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
+EOT
+        MIMEObj  => $args{'Message'},
+        LogLevel => 'error'
+    );
 
-    $value =~ s/\s+$//;
+    # Also notify the requestor that his request has been dropped.
+    if ($args{'Requestor'} ne RT->Config->Get('OwnerEmail')) {
+    MailError(
+        To          => $args{'Requestor'},
+        Subject     => "Could not load a valid user",
+        Explanation => <<EOT,
+RT could not load a valid user, and RT's configuration does not allow
+for the creation of a new user for your email.
 
-    my ( $tmp, @chunks ) = ( '', () );
-    while ( length $value ) {
-        my $char = substr( $value, 0, 1, '' );
-        my $octets = Encode::encode( $charset, $char );
-        if ( length($tmp) + length($octets) > $max ) {
-            push @chunks, $tmp;
-            $tmp = '';
-        }
-        $tmp .= $octets;
+EOT
+        MIMEObj  => $args{'Message'},
+        LogLevel => 'error'
+    );
     }
-    push @chunks, $tmp if length $tmp;
-
-    # encode an join chuncks
-    $value = join "\n ",
-        map MIME::Words::encode_mimeword( $_, $encoding, $charset ),
-        @chunks;
-    return ($value);
 }
 
 sub CreateUser {
@@ -856,7 +612,6 @@ sub CreateUser {
 }
 
 
-
 =head2 ParseCcAddressesFromHead HASH
 
 Takes a hash containing QueueObj, Head and CurrentUser objects.
@@ -916,135 +671,236 @@ sub ParseSenderAddressFromHead {
         push @errors, "$header: $addr_line";
     }
 
-    return (undef, undef, @errors);
+    return (undef, undef, @errors);
+}
+
+=head2 ParseErrorsToAddressFromHead HEAD
+
+Takes a MIME::Header object. Return a single value : user at host
+of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
+From:, Sender)
+
+=cut
+
+sub ParseErrorsToAddressFromHead {
+    my $head = shift;
+
+    #Figure out who's sending this message.
+
+    foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
+
+        # If there's a header of that name
+        my $headerobj = Encode::decode( "UTF-8", $head->get($header) );
+        if ($headerobj) {
+            my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
+
+            # If it's got actual useful content...
+            return ($addr) if ($addr);
+        }
+    }
+}
+
+
+
+=head2 ParseAddressFromHeader ADDRESS
+
+Takes an address from C<$head->get('Line')> and returns a tuple: user at host, friendly name
+
+=cut
+
+sub ParseAddressFromHeader {
+    my $Addr = shift;
+
+    # Some broken mailers send:  ""Vincent, Jesse"" <jesse at fsck.com>. Hate
+    $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;
+    my @Addresses = RT::EmailParser->ParseEmailAddress($Addr);
+
+    my ($AddrObj) = grep ref $_, @Addresses;
+    unless ( $AddrObj ) {
+        return ( undef, undef );
+    }
+
+    return ( $AddrObj->address, $AddrObj->phrase );
+}
+
+=head2 _HandleMachineGeneratedMail
+
+Takes named params:
+    Message
+    ErrorsTo
+    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",
+"This message appears to be a loop (boolean)" );
+
+=cut
+
+sub _HandleMachineGeneratedMail {
+    my %args = ( Message => undef, ErrorsTo => undef, Subject => undef, MessageId => undef, @_ );
+    my $head = $args{'Message'}->head;
+    my $ErrorsTo = $args{'ErrorsTo'};
+
+    my $IsBounce = CheckForBounce($head);
+
+    my $IsAutoGenerated = CheckForAutoGenerated($head);
+
+    my $IsSuspiciousSender = CheckForSuspiciousSender($head);
+
+    my $IsALoop = CheckForLoops($head);
+
+    my $SquelchReplies = 0;
+
+    my $owner_mail = RT->Config->Get('OwnerEmail');
+
+    #If the message is autogenerated, we need to know, so we can not
+    # send mail to the sender
+    if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
+        $SquelchReplies = 1;
+        $ErrorsTo       = $owner_mail;
+    }
+
+    # Warn someone if it's a loop, before we drop it on the ground
+    if ($IsALoop) {
+        $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
+
+        #Should we mail it to RTOwner?
+        if ( RT->Config->Get('LoopsToRTOwner') ) {
+            MailError(
+                To          => $owner_mail,
+                Subject     => "RT Bounce: ".$args{'Subject'},
+                Explanation => "RT thinks this message may be a bounce",
+                MIMEObj     => $args{Message}
+            );
+        }
+
+        #Do we actually want to store it?
+        return ( 0, $ErrorsTo, "Message Bounced", $IsALoop )
+            unless RT->Config->Get('StoreLoops');
+    }
+
+    # Squelch replies if necessary
+    # Don't let the user stuff the RT-Squelch-Replies-To header.
+    if ( $head->get('RT-Squelch-Replies-To') ) {
+        $head->replace(
+            'RT-Relocated-Squelch-Replies-To',
+            $head->get('RT-Squelch-Replies-To')
+        );
+        $head->delete('RT-Squelch-Replies-To');
+    }
+
+    if ($SquelchReplies) {
+
+        # Squelch replies to the sender, and also leave a clue to
+        # allow us to squelch ALL outbound messages. This way we
+        # can punt the logic of "what to do when we get a bounce"
+        # 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->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
+        $head->replace( 'RT-DetectedAutoGenerated', 'true' );
+    }
+    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
+}
+
+=head2 CheckForLoops HEAD
+
+Takes a HEAD object of L<MIME::Head> class and returns true if the
+message's been sent by this RT instance. Uses "X-RT-Loop-Prevention"
+field of the head for test.
+
+=cut
+
+sub CheckForLoops {
+    my $head = shift;
+
+    # If this instance of RT sent it our, we don't want to take it in
+    my $RTLoop = Encode::decode( "UTF-8", $head->get("X-RT-Loop-Prevention") || "" );
+    chomp ($RTLoop); # remove that newline
+    if ( $RTLoop eq RT->Config->Get('rtname') ) {
+        return 1;
+    }
+
+    # TODO: We might not trap the case where RT instance A sends a mail
+    # to RT instance B which sends a mail to ...
+    return undef;
 }
 
-=head2 ParseErrorsToAddressFromHead HEAD
+=head2 CheckForSuspiciousSender HEAD
 
-Takes a MIME::Header object. Return a single value : user at host
-of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
-From:, Sender)
+Takes a HEAD object of L<MIME::Head> class and returns true if sender
+is suspicious. Suspicious means mailer daemon.
+
+See also L</ParseSenderAddressFromHead>.
 
 =cut
 
-sub ParseErrorsToAddressFromHead {
+sub CheckForSuspiciousSender {
     my $head = shift;
 
-    #Figure out who's sending this message.
-
-    foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
-
-        # If there's a header of that name
-        my $headerobj = Encode::decode( "UTF-8", $head->get($header) );
-        if ($headerobj) {
-            my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
-
-            # If it's got actual useful content...
-            return ($addr) if ($addr);
-        }
-    }
-}
-
-
+    #if it's from a postmaster or mailer daemon, it's likely a bounce.
 
-=head2 ParseAddressFromHeader ADDRESS
+    #TODO: better algorithms needed here - there is no standards for
+    #bounces, so it's very difficult to separate them from anything
+    #else.  At the other hand, the Return-To address is only ment to be
+    #used as an error channel, we might want to put up a separate
+    #Return-To address which is treated differently.
 
-Takes an address from C<$head->get('Line')> and returns a tuple: user at host, friendly name
+    #TODO: search through the whole email and find the right Ticket ID.
 
-=cut
+    my ( $From, $junk ) = ParseSenderAddressFromHead($head);
 
-sub ParseAddressFromHeader {
-    my $Addr = shift;
+    # If unparseable (non-ASCII), $From can come back undef
+    return undef if not defined $From;
 
-    # Some broken mailers send:  ""Vincent, Jesse"" <jesse at fsck.com>. Hate
-    $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;
-    my @Addresses = RT::EmailParser->ParseEmailAddress($Addr);
+    if (   ( $From =~ /^mailer-daemon\@/i )
+        or ( $From =~ /^postmaster\@/i )
+        or ( $From eq "" ))
+    {
+        return (1);
 
-    my ($AddrObj) = grep ref $_, @Addresses;
-    unless ( $AddrObj ) {
-        return ( undef, undef );
     }
 
-    return ( $AddrObj->address, $AddrObj->phrase );
+    return undef;
 }
 
-=head2 DeleteRecipientsFromHead HEAD RECIPIENTS
+=head2 CheckForAutoGenerated HEAD
 
-Gets a head object and list of addresses.
-Deletes addresses from To, Cc or Bcc fields.
+Takes a HEAD object of L<MIME::Head> class and returns true if message is
+autogenerated. Checks C<Precedence>, C<Auto-Submitted>, and
+C<X-FC-Machinegenerated> fields of the head in tests.
 
 =cut
 
-sub DeleteRecipientsFromHead {
+sub CheckForAutoGenerated {
     my $head = shift;
-    my %skip = map { lc $_ => 1 } @_;
 
-    foreach my $field ( qw(To Cc Bcc) ) {
-        $head->replace( $field => Encode::encode( "UTF-8",
-            join ', ', map $_->format, grep !$skip{ lc $_->address },
-                Email::Address->parse( Encode::decode( "UTF-8", $head->get( $field ) ) ) )
-        );
+    if (grep { /^(bulk|junk)/i } $head->get_all("Precedence")) {
+        return (1);
     }
-}
-
-sub GenMessageId {
-    my %args = (
-        Ticket      => undef,
-        Scrip       => undef,
-        ScripAction => undef,
-        @_
-    );
-    my $org = RT->Config->Get('Organization');
-    my $ticket_id = ( ref $args{'Ticket'}? $args{'Ticket'}->id : $args{'Ticket'} ) || 0;
-    my $scrip_id = ( ref $args{'Scrip'}? $args{'Scrip'}->id : $args{'Scrip'} ) || 0;
-    my $sent = ( ref $args{'ScripAction'}? $args{'ScripAction'}->{'_Message_ID'} : 0 ) || 0;
-
-    return "<rt-". $RT::VERSION ."-". $$ ."-". CORE::time() ."-". int(rand(2000)) .'.'
-        . $ticket_id ."-". $scrip_id ."-". $sent ."@". $org .">" ;
-}
-
-sub SetInReplyTo {
-    my %args = (
-        Message   => undef,
-        InReplyTo => undef,
-        Ticket    => undef,
-        @_
-    );
-    return unless $args{'Message'} && $args{'InReplyTo'};
-
-    my $get_header = sub {
-        my @res;
-        if ( $args{'InReplyTo'}->isa('MIME::Entity') ) {
-            @res = map {Encode::decode("UTF-8", $_)} $args{'InReplyTo'}->head->get( shift );
-        } else {
-            @res = $args{'InReplyTo'}->GetHeader( shift ) || '';
-        }
-        return grep length, map { split /\s+/m, $_ } grep defined, @res;
-    };
 
-    my @id = $get_header->('Message-ID');
-    #XXX: custom header should begin with X- otherwise is violation of the standard
-    my @rtid = $get_header->('RT-Message-ID');
-    my @references = $get_header->('References');
-    unless ( @references ) {
-        @references = $get_header->('In-Reply-To');
+    # Per RFC3834, any Auto-Submitted header which is not "no" means
+    # it is auto-generated.
+    my $AutoSubmitted = $head->get("Auto-Submitted") || "";
+    if ( length $AutoSubmitted and $AutoSubmitted ne "no" ) {
+        return (1);
     }
-    push @references, @id, @rtid;
-    if ( $args{'Ticket'} ) {
-        my $pseudo_ref = PseudoReference( $args{'Ticket'} );
-        push @references, $pseudo_ref unless grep $_ eq $pseudo_ref, @references;
+
+    # First Class mailer uses this as a clue.
+    my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
+    if ( $FCJunk =~ /^true/i ) {
+        return (1);
     }
-    splice @references, 4, -6
-        if @references > 10;
 
-    my $mail = $args{'Message'};
-    $mail->head->replace( 'In-Reply-To' => Encode::encode( "UTF-8", join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
-    $mail->head->replace( 'References' => Encode::encode( "UTF-8", join ' ', @references) );
+    return (0);
 }
 
-sub PseudoReference {
-    my $ticket = shift;
-    return '<RT-Ticket-'. $ticket->id .'@'. RT->Config->Get('Organization') .'>';
+
+sub CheckForBounce {
+    my $head = shift;
+
+    my $ReturnPath = $head->get("Return-path") || "";
+    return ( $ReturnPath =~ /<>/ );
 }
 
 =head2 ExtractTicketId
@@ -1100,642 +956,781 @@ sub ParseTicketId {
     return $id;
 }
 
-sub AddSubjectTag {
-    my $subject = shift;
-    my $ticket  = shift;
-    unless ( ref $ticket ) {
-        my $tmp = RT::Ticket->new( RT->SystemUser );
-        $tmp->Load( $ticket );
-        $ticket = $tmp;
+sub _RunUnsafeAction {
+    my %args = (
+        Action      => undef,
+        ErrorsTo    => undef,
+        Message     => undef,
+        Ticket      => undef,
+        CurrentUser => undef,
+        @_
+    );
+
+    my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
+
+    if ( $args{'Action'} =~ /^take$/i ) {
+        my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{'CurrentUser'}->id );
+        unless ($status) {
+            MailError(
+                To          => $args{'ErrorsTo'},
+                Subject     => "Ticket not taken",
+                Explanation => $msg,
+                MIMEObj     => $args{'Message'}
+            );
+            return ( 0, "Ticket not taken, by email From: $From" );
+        }
+    } elsif ( $args{'Action'} =~ /^resolve$/i ) {
+        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, by email From: $From" );
+            }
+        }
+    } else {
+        return ( 0, "Not supported unsafe action $args{'Action'}, by email From: $From", $args{'Ticket'} );
     }
-    my $id = $ticket->id;
-    my $queue_tag = $ticket->QueueObj->SubjectTag;
+    return ( 1, "Success" );
+}
 
-    my $tag_re = RT->Config->Get('EmailSubjectTagRegex');
-    unless ( $tag_re ) {
-        my $tag = $queue_tag || RT->Config->Get('rtname');
-        $tag_re = qr/\Q$tag\E/;
-    } elsif ( $queue_tag ) {
-        $tag_re = qr/$tag_re|\Q$queue_tag\E/;
+=head2 MailError PARAM HASH
+
+Sends an error message. Takes a param hash:
+
+=over 4
+
+=item From - sender's address, by default is 'CorrespondAddress';
+
+=item To - recipient, by default is 'OwnerEmail';
+
+=item Bcc - optional Bcc recipients;
+
+=item Subject - subject of the message, default is 'There has been an error';
+
+=item Explanation - main content of the error, default value is 'Unexplained error';
+
+=item MIMEObj - optional MIME entity that's attached to the error mail, as well we
+add 'In-Reply-To' field to the error that points to this message.
+
+=item Attach - optional text that attached to the error as 'message/rfc822' part.
+
+=item LogLevel - log level under which we should write the subject and
+explanation message into the log, by default we log it as critical.
+
+=back
+
+=cut
+
+sub MailError {
+    my %args = (
+        To          => RT->Config->Get('OwnerEmail'),
+        Bcc         => undef,
+        From        => RT->Config->Get('CorrespondAddress'),
+        Subject     => 'There has been an error',
+        Explanation => 'Unexplained error',
+        MIMEObj     => undef,
+        Attach      => undef,
+        LogLevel    => 'crit',
+        @_
+    );
+
+    $RT::Logger->log(
+        level   => $args{'LogLevel'},
+        message => "$args{Subject}: $args{'Explanation'}",
+    ) if $args{'LogLevel'};
+
+    # the colons are necessary to make ->build include non-standard headers
+    my %entity_args = (
+        Type                    => "multipart/mixed",
+        From                    => Encode::encode( "UTF-8", $args{'From'} ),
+        Bcc                     => Encode::encode( "UTF-8", $args{'Bcc'} ),
+        To                      => Encode::encode( "UTF-8", $args{'To'} ),
+        Subject                 => EncodeToMIME( String => $args{'Subject'} ),
+        'X-RT-Loop-Prevention:' => Encode::encode( "UTF-8", RT->Config->Get('rtname') ),
+    );
+
+    # only set precedence if the sysadmin wants us to
+    if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) {
+        $entity_args{'Precedence:'} =
+            Encode::encode( "UTF-8", RT->Config->Get('DefaultErrorMailPrecedence') );
     }
-    return $subject if $subject =~ /\[$tag_re\s+#$id\]/;
 
-    $subject =~ s/(\r\n|\n|\s)/ /g;
-    chomp $subject;
-    return "[". ($queue_tag || RT->Config->Get('rtname')) ." #$id] $subject";
-}
+    my $entity = MIME::Entity->build(%entity_args);
+    SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} );
 
+    $entity->attach(
+        Type    => "text/plain",
+        Charset => "UTF-8",
+        Data    => Encode::encode( "UTF-8", $args{'Explanation'} . "\n" ),
+    );
 
-=head2 Gateway ARGSREF
+    if ( $args{'MIMEObj'} ) {
+        $args{'MIMEObj'}->sync_headers;
+        $entity->add_part( $args{'MIMEObj'} );
+    }
 
+    if ( $args{'Attach'} ) {
+        $entity->attach( Data => Encode::encode( "UTF-8", $args{'Attach'} ), Type => 'message/rfc822' );
 
-Takes parameters:
+    }
 
-    action
-    queue
-    message
+    SendEmail( Entity => $entity, Bounce => 1 );
+}
 
 
-This performs all the "guts" of the mail rt-mailgate program, and is
-designed to be called from the web interface with a message, user
-object, and so on.
+=head2 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ]
 
-Can also take an optional 'ticket' parameter; this ticket id overrides
-any ticket id found in the subject.
+Sends an email (passed as a L<MIME::Entity> object C<ENTITY>) using
+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.
 
-Returns:
+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.
 
-    An array of:
+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.
 
-    (status code, message, optional ticket object)
+Returns 1 on success, 0 on error or -1 if message has no recipients
+and hasn't been sent.
 
-    status code is a numeric value.
+=head3 Signing and Encrypting
 
-      for temporary failures, the status code should be -75
+This function as well signs and/or encrypts the message according to
+headers of a transaction's attachment or properties of a ticket's queue.
+To get full access to the configuration Ticket and/or Transaction
+arguments must be provided, but you can force behaviour using Sign
+and/or Encrypt arguments.
 
-      for permanent failures which are handled by RT, the status code
-      should be 0
+The following precedence of arguments are used to figure out if
+the message should be encrypted and/or signed:
 
-      for succces, the status code should be 1
+* if Sign or Encrypt argument is defined then its value is used
 
+* else if Transaction's first attachment has X-RT-Sign or X-RT-Encrypt
+header field then it's value is used
 
+* else properties of a queue of the Ticket are used.
 
 =cut
 
-sub _LoadPlugins {
-    my @mail_plugins = @_;
+sub SendEmail {
+    my (%args) = (
+        Entity => undef,
+        Bounce => 0,
+        Ticket => undef,
+        Transaction => undef,
+        @_,
+    );
 
-    my @res;
-    foreach my $plugin (@mail_plugins) {
-        if ( ref($plugin) eq "CODE" ) {
-            push @res, $plugin;
-        } elsif ( !ref $plugin ) {
-            my $Class = $plugin;
-            $Class = "RT::Interface::Email::" . $Class
-                unless $Class =~ /^RT::/;
-            $Class->require or
-                do { $RT::Logger->error("Couldn't load $Class: $@"); next };
+    my $TicketObj = $args{'Ticket'};
+    my $TransactionObj = $args{'Transaction'};
 
-            no strict 'refs';
-            unless ( defined *{ $Class . "::GetCurrentUser" }{CODE} ) {
-                $RT::Logger->crit( "No GetCurrentUser code found in $Class module");
-                next;
-            }
-            push @res, $Class;
-        } else {
-            $RT::Logger->crit( "$plugin - is not class name or code reference");
-        }
+    unless ( $args{'Entity'} ) {
+        $RT::Logger->crit( "Could not send mail without 'Entity' object" );
+        return 0;
     }
-    return @res;
-}
 
-sub Gateway {
-    my $argsref = shift;
-    my %args    = (
-        action  => 'correspond',
-        queue   => '1',
-        ticket  => undef,
-        message => undef,
-        %$argsref
-    );
+    my $msgid = Encode::decode( "UTF-8", $args{'Entity'}->head->get('Message-ID') || '' );
+    chomp $msgid;
+    
+    # If we don't have any recipients to send to, don't send a message;
+    unless ( $args{'Entity'}->head->get('To')
+        || $args{'Entity'}->head->get('Cc')
+        || $args{'Entity'}->head->get('Bcc') )
+    {
+        $RT::Logger->info( $msgid . " No recipients found. Not sending." );
+        return -1;
+    }
 
-    my $SystemTicket;
-    my $Right;
+    if ($args{'Entity'}->head->get('X-RT-Squelch')) {
+        $RT::Logger->info( $msgid . " Squelch header found. Not sending." );
+        return -1;
+    }
 
-    # Validate the action
-    my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
-    unless ($status) {
-        return (
-            -75,
-            "Invalid 'action' parameter "
-                . $actions[0]
-                . " for queue "
-                . $args{'queue'},
-            undef
-        );
+    if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
+        and !$args{'Entity'}->head->get("Precedence")
+    ) {
+        $args{'Entity'}->head->replace( 'Precedence', Encode::encode("UTF-8",$precedence) );
     }
 
-    my $parser = RT::EmailParser->new();
-    $parser->SmartParseMIMEEntityFromScalar(
-        Message => $args{'message'},
-        Decode => 0,
-        Exact => 1,
-    );
+    if ( $TransactionObj && !$TicketObj
+        && $TransactionObj->ObjectType eq 'RT::Ticket' )
+    {
+        $TicketObj = $TransactionObj->Object;
+    }
 
-    my $Message = $parser->Entity();
-    unless ($Message) {
-        MailError(
-            Subject     => "RT Bounce: Unparseable message",
-            Explanation => "RT couldn't process the message below",
-            Attach      => $args{'message'}
-        );
+    my $head = $args{'Entity'}->head;
+    unless ( $head->get('Date') ) {
+        require RT::Date;
+        my $date = RT::Date->new( RT->SystemUser );
+        $date->SetToNow;
+        $head->replace( 'Date', Encode::encode("UTF-8",$date->RFC2822( Timezone => 'server' ) ) );
+    }
+    unless ( $head->get('MIME-Version') ) {
+        # We should never have to set the MIME-Version header
+        $head->replace( 'MIME-Version', '1.0' );
+    }
+    unless ( $head->get('Content-Transfer-Encoding') ) {
+        # fsck.com #5959: Since RT sends 8bit mail, we should say so.
+        $head->replace( 'Content-Transfer-Encoding', '8bit' );
+    }
 
-        return ( 0,
-            "Failed to parse this message. Something is likely badly wrong with the message"
+    if ( RT->Config->Get('Crypt')->{'Enable'} ) {
+        %args = WillSignEncrypt(
+            %args,
+            Attachment => $TransactionObj ? $TransactionObj->Attachments->First : undef,
+            Ticket     => $TicketObj,
         );
+        my $res = SignEncrypt( %args );
+        return $res unless $res > 0;
     }
 
-    my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
-    push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
-    @mail_plugins = _LoadPlugins( @mail_plugins );
+    my $mail_command = RT->Config->Get('MailCommand');
 
-    #Set up a queue object
-    my $SystemQueueObj = RT::Queue->new( RT->SystemUser );
-    $SystemQueueObj->Load( $args{'queue'} );
+    # if it is a sub routine, we just return it;
+    return $mail_command->($args{'Entity'}) if UNIVERSAL::isa( $mail_command, 'CODE' );
 
-    my %skip_plugin;
-    foreach my $class( grep !ref, @mail_plugins ) {
-        # check if we should apply filter before decoding
-        my $check_cb = do {
-            no strict 'refs';
-            *{ $class . "::ApplyBeforeDecode" }{CODE};
-        };
-        next unless defined $check_cb;
-        next unless $check_cb->(
-            Message       => $Message,
-            RawMessageRef => \$args{'message'},
-            Queue         => $SystemQueueObj,
-            Actions       => \@actions,
-        );
+    if ( $mail_command eq 'sendmailpipe' ) {
+        my $path = RT->Config->Get('SendmailPath');
+        my @args = shellwords(RT->Config->Get('SendmailArguments'));
+        push @args, "-t" unless grep {$_ eq "-t"} @args;
+
+        # SetOutgoingMailFrom and bounces conflict, since they both want -f
+        if ( $args{'Bounce'} ) {
+            push @args, shellwords(RT->Config->Get('SendmailBounceArguments'));
+        } elsif ( my $MailFrom = RT->Config->Get('SetOutgoingMailFrom') ) {
+            my $OutgoingMailAddress = $MailFrom =~ /\@/ ? $MailFrom : undef;
+            my $Overrides = RT->Config->Get('OverrideOutgoingMailFrom') || {};
+
+            if ($TicketObj) {
+                my $Queue = $TicketObj->QueueObj;
+                my $QueueAddressOverride = $Overrides->{$Queue->id}
+                    || $Overrides->{$Queue->Name};
+
+                if ($QueueAddressOverride) {
+                    $OutgoingMailAddress = $QueueAddressOverride;
+                } else {
+                    $OutgoingMailAddress ||= $Queue->CorrespondAddress
+                        || RT->Config->Get('CorrespondAddress');
+                }
+            }
+            elsif ($Overrides->{'Default'}) {
+                $OutgoingMailAddress = $Overrides->{'Default'};
+            }
+
+            push @args, "-f", $OutgoingMailAddress
+                if $OutgoingMailAddress;
+        }
+
+        # VERP
+        if ( $TransactionObj and
+             my $prefix = RT->Config->Get('VERPPrefix') and
+             my $domain = RT->Config->Get('VERPDomain') )
+        {
+            my $from = $TransactionObj->CreatorObj->EmailAddress;
+            $from =~ s/@/=/g;
+            $from =~ s/\s//g;
+            push @args, "-f", "$prefix$from\@$domain";
+        }
 
-        $skip_plugin{ $class }++;
+        eval {
+            # don't ignore CHLD signal to get proper exit code
+            local $SIG{'CHLD'} = 'DEFAULT';
 
-        my $Code = do {
-            no strict 'refs';
-            *{ $class . "::GetCurrentUser" }{CODE};
+            # 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);
+            close $mail or die "close pipe failed: $!";
+
+            waitpid($pid, 0);
+            if ($?) {
+                # sendmail exit statuses mostly errors with data not software
+                # TODO: status parsing: core dump, exit on signal or EX_*
+                my $msg = "$msgid: `$path @args` exited with code ". ($?>>8);
+                $msg = ", interrupted by signal ". ($?&127) if $?&127;
+                $RT::Logger->error( $msg );
+                die $msg;
+            }
         };
-        my ($status, $msg) = $Code->(
-            Message       => $Message,
-            RawMessageRef => \$args{'message'},
-            Queue         => $SystemQueueObj,
-            Actions       => \@actions,
-        );
-        next if $status > 0;
+        if ( $@ ) {
+            $RT::Logger->crit( "$msgid: Could not send mail with command `$path @args`: " . $@ );
+            if ( $TicketObj ) {
+                _RecordSendEmailFailure( $TicketObj );
+            }
+            return 0;
+        }
+    }
+    else {
+        local ($ENV{'MAILADDRESS'}, $ENV{'PERL_MAILERS'});
 
-        if ( $status == -2 ) {
-            return (1, $msg, undef);
-        } elsif ( $status == -1 ) {
-            return (0, $msg, undef);
+        my @mailer_args = ($mail_command);
+        if ( $mail_command eq 'sendmail' ) {
+            $ENV{'PERL_MAILERS'} = RT->Config->Get('SendmailPath');
+            push @mailer_args, grep {$_ ne "-t"}
+                split(/\s+/, RT->Config->Get('SendmailArguments'));
+        } elsif ( $mail_command eq 'testfile' ) {
+            unless ($Mail::Mailer::testfile::config{outfile}) {
+                $Mail::Mailer::testfile::config{outfile} = File::Temp->new;
+                $RT::Logger->info("Storing outgoing emails in $Mail::Mailer::testfile::config{outfile}");
+            }
+        } else {
+            push @mailer_args, RT->Config->Get('MailParams');
+        }
+
+        unless ( $args{'Entity'}->send( @mailer_args ) ) {
+            $RT::Logger->crit( "$msgid: Could not send mail." );
+            if ( $TicketObj ) {
+                _RecordSendEmailFailure( $TicketObj );
+            }
+            return 0;
         }
     }
-    @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
-    $parser->_DecodeBodies;
-    $parser->RescueOutlook;
-    $parser->_PostProcessNewEntity;
+    return 1;
+}
 
-    my $head = $Message->head;
-    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
-    my $Sender = (ParseSenderAddressFromHead( $head ))[0];
-    my $From = Encode::decode( "UTF-8", $head->get("From") );
-    chomp $From if defined $From;
+=head2 PrepareEmailUsingTemplate Template => '', Arguments => {}
 
-    my $MessageId = Encode::decode( "UTF-8", $head->get('Message-ID') )
-        || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
+Loads a template. Parses it using arguments if it's not empty.
+Returns a tuple (L<RT::Template> object, error message).
 
-    #Pull apart the subject line
-    my $Subject = Encode::decode( "UTF-8", $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
+Note that even if a template object is returned MIMEObj method
+may return undef for empty templates.
+
+=cut
+
+sub PrepareEmailUsingTemplate {
+    my %args = (
+        Template => '',
+        Arguments => {},
+        @_
     );
 
-    # 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 );
+    my $template = RT::Template->new( RT->SystemUser );
+    $template->LoadGlobalTemplate( $args{'Template'} );
+    unless ( $template->id ) {
+        return (undef, "Couldn't load template '". $args{'Template'} ."'");
     }
-    # }}}
+    return $template if $template->IsEmpty;
 
-    $args{'ticket'} ||= ExtractTicketId( $Message );
+    my ($status, $msg) = $template->Parse( %{ $args{'Arguments'} } );
+    return (undef, $msg) unless $status;
 
-    # ExtractTicketId may have been overridden, and edited the Subject
-    my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
-    chomp $NewSubject;
+    return $template;
+}
 
-    $SystemTicket = RT::Ticket->new( RT->SystemUser );
-    $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
-    if ( $SystemTicket->id ) {
-        $Right = 'ReplyToTicket';
-    } else {
-        $Right = 'CreateTicket';
-    }
+=head2 SendEmailUsingTemplate Template => '', Arguments => {}, From => CorrespondAddress, To => '', Cc => '', Bcc => ''
 
-    # We can safely have no queue of we have a known-good ticket
-    unless ( $SystemTicket->id || $SystemQueueObj->id ) {
-        return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
-    }
+Sends email using a template, takes name of template, arguments for it and recipients.
 
-    my ($AuthStat, $CurrentUser, $error) = GetAuthenticationLevel(
-        MailPlugins   => \@mail_plugins,
-        Actions       => \@actions,
-        Message       => $Message,
-        RawMessageRef => \$args{message},
-        SystemTicket  => $SystemTicket,
-        SystemQueue   => $SystemQueueObj,
-    );
+=cut
 
-    # If authentication fails and no new user was created, get out.
-    if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
+sub SendEmailUsingTemplate {
+    my %args = (
+        Template => '',
+        Arguments => {},
+        To => undef,
+        Cc => undef,
+        Bcc => undef,
+        From => RT->Config->Get('CorrespondAddress'),
+        InReplyTo => undef,
+        ExtraHeaders => {},
+        @_
+    );
 
-        # If the plugins refused to create one, they lose.
-        unless ( $AuthStat == -1 ) {
-            _NoAuthorizedUserFound(
-                Right     => $Right,
-                Message   => $Message,
-                Requestor => $ErrorsTo,
-                Queue     => $args{'queue'}
-            );
+    my ($template, $msg) = PrepareEmailUsingTemplate( %args );
+    return (0, $msg) unless $template;
 
-        }
-        return ( 0, "Could not load a valid user", undef );
+    my $mail = $template->MIMEObj;
+    unless ( $mail ) {
+        $RT::Logger->info("Message is not sent as template #". $template->id ." is empty");
+        return -1;
     }
 
-    # If we got a user, but they don't have the right to say things
-    if ( $AuthStat == 0 ) {
-        MailError(
-            To          => $ErrorsTo,
-            Subject     => "Permission Denied",
-            Explanation =>
-                "You do not have permission to communicate with RT",
-            MIMEObj => $Message
-        );
-        return (
-            0,
-            ($CurrentUser->EmailAddress || $CurrentUser->Name)
-            . " ($Sender) tried to submit a message to "
-                . $args{'Queue'}
-                . " without permission.",
-            undef
-        );
-    }
+    $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ $_ } ) )
+        foreach grep defined $args{$_}, qw(To Cc Bcc From);
 
+    $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ExtraHeaders}{$_} ) )
+        foreach keys %{ $args{ExtraHeaders} };
 
-    unless ($should_store_machine_generated_message) {
-        return ( 0, $result, undef );
-    }
+    SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
 
-    $head->replace('X-RT-Interface' => 'Email');
+    return SendEmail( Entity => $mail );
+}
 
-    # if plugin's updated SystemTicket then update arguments
-    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
+=head2 GetForwardFrom Ticket => undef, Transaction => undef
 
-    my $Ticket = RT::Ticket->new($CurrentUser);
+Resolve the From field to use in forward mail
 
-    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
-    {
+=cut
 
-        my @Cc;
-        my @Requestors = ( $CurrentUser->id );
+sub GetForwardFrom {
+    my %args   = ( Ticket => undef, Transaction => undef, @_ );
+    my $txn    = $args{Transaction};
+    my $ticket = $args{Ticket} || $txn->Object;
 
-        if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
-            @Cc = ParseCcAddressesFromHead(
-                Head        => $head,
-                CurrentUser => $CurrentUser,
-                QueueObj    => $SystemQueueObj
-            );
-        }
+    if ( RT->Config->Get('ForwardFromUser') ) {
+        return ( $txn || $ticket )->CurrentUser->EmailAddress;
+    }
+    else {
+        return $ticket->QueueObj->CorrespondAddress
+          || RT->Config->Get('CorrespondAddress');
+    }
+}
 
-        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
-            Queue     => $SystemQueueObj->Id,
-            Subject   => $NewSubject,
-            Requestor => \@Requestors,
-            Cc        => \@Cc,
-            MIMEObj   => $Message
-        );
-        if ( $id == 0 ) {
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "Ticket creation failed: $Subject",
-                Explanation => $ErrStr,
-                MIMEObj     => $Message
-            );
-            return ( 0, "Ticket creation From: $From failed: $ErrStr", $Ticket );
-        }
+=head2 GetForwardAttachments Ticket => undef, Transaction => undef
 
-        # strip comments&corresponds from the actions we don't need
-        # to record them if we've created the ticket just now
-        @actions = grep !/^(comment|correspond)$/, @actions;
-        $args{'ticket'} = $id;
+Resolve the Attachments to forward
 
-    } elsif ( $args{'ticket'} ) {
+=cut
 
-        $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",
-                Explanation => $error,
-                MIMEObj     => $Message
-            );
+sub GetForwardAttachments {
+    my %args   = ( Ticket => undef, Transaction => undef, @_ );
+    my $txn    = $args{Transaction};
+    my $ticket = $args{Ticket} || $txn->Object;
 
-            return ( 0, $error );
-        }
-        $args{'ticket'} = $Ticket->id;
-    } else {
-        return ( 1, "Success", $Ticket );
+    my $attachments = RT::Attachments->new( $ticket->CurrentUser );
+    if ($txn) {
+        $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
+    }
+    else {
+        $attachments->LimitByTicket( $ticket->id );
+        $attachments->Limit(
+            ALIAS         => $attachments->TransactionAlias,
+            FIELD         => 'Type',
+            OPERATOR      => 'IN',
+            VALUE         => [ qw(Create Correspond) ],
+        );
     }
+    return $attachments;
+}
 
-    # }}}
+sub WillSignEncrypt {
+    my %args = @_;
+    my $attachment = delete $args{Attachment};
+    my $ticket     = delete $args{Ticket};
 
-    my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
-    foreach my $action (@actions) {
+    if ( not RT->Config->Get('Crypt')->{'Enable'} ) {
+        $args{Sign} = $args{Encrypt} = 0;
+        return wantarray ? %args : 0;
+    }
 
-        #   If the action is comment, add a comment.
-        if ( $action =~ /^(?:comment|correspond)$/i ) {
-            my $method = ucfirst lc $action;
-            my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );
-            unless ($status) {
+    for my $argument ( qw(Sign Encrypt) ) {
+        next if defined $args{ $argument };
 
-                #Warn the sender that we couldn't actually submit the comment.
-                MailError(
-                    To          => $ErrorsTo,
-                    Subject     => "Message not recorded ($method): $Subject",
-                    Explanation => $msg,
-                    MIMEObj     => $Message
-                );
-                return ( 0, "Message From: $From not recorded: $msg", $Ticket );
-            }
-        } elsif ($unsafe_actions) {
-            my ( $status, $msg ) = _RunUnsafeAction(
-                Action      => $action,
-                ErrorsTo    => $ErrorsTo,
-                Message     => $Message,
-                Ticket      => $Ticket,
-                CurrentUser => $CurrentUser,
-            );
-            return ($status, $msg, $Ticket) unless $status == 1;
+        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 ( 1, "Success", $Ticket );
+
+    return wantarray ? %args : ($args{Sign} || $args{Encrypt});
 }
 
-=head2 GetAuthenticationLevel
+=head2 SignEncrypt Entity => undef, Sign => 0, Encrypt => 0
 
-    # Authentication Level
-    # -1 - Get out.  this user has been explicitly declined
-    # 0 - User may not do anything (Not used at the moment)
-    # 1 - Normal user
-    # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate
+Signs and encrypts message using L<RT::Crypt>, but as well handle errors
+with users' keys.
+
+If a recipient has no key or has other problems with it, then the
+unction sends a error to him using 'Error: public key' template.
+Also, notifies RT's owner using template 'Error to RT owner: public key'
+to inform that there are problems with users' keys. Then we filter
+all bad recipients and retry.
+
+Returns 1 on success, 0 on error and -1 if all recipients are bad and
+had been filtered out.
 
 =cut
 
-sub GetAuthenticationLevel {
+sub SignEncrypt {
     my %args = (
-        MailPlugins   => [],
-        Actions       => [],
-        Message       => undef,
-        RawMessageRef => undef,
-        SystemTicket  => undef,
-        SystemQueue   => undef,
-        @_,
+        Entity => undef,
+        Sign => 0,
+        Encrypt => 0,
+        @_
     );
+    return 1 unless $args{'Sign'} || $args{'Encrypt'};
 
-    my ( $CurrentUser, $AuthStat, $error );
-
-    # Initalize AuthStat so comparisons work correctly
-    $AuthStat = -9999999;
-
-    # if plugin returns AuthStat -2 we skip action
-    # NOTE: this is experimental API and it would be changed
-    my %skip_action = ();
+    my $msgid = Encode::decode( "UTF-8", $args{'Entity'}->head->get('Message-ID') || '' );
+    chomp $msgid;
 
-    # Since this needs loading, no matter what
-    foreach (@{ $args{MailPlugins} }) {
-        my ($Code, $NewAuthStat);
-        if ( ref($_) eq "CODE" ) {
-            $Code = $_;
-        } else {
-            no strict 'refs';
-            $Code = *{ $_ . "::GetCurrentUser" }{CODE};
-        }
+    $RT::Logger->debug("$msgid Signing message") if $args{'Sign'};
+    $RT::Logger->debug("$msgid Encrypting message") if $args{'Encrypt'};
 
-        foreach my $action (@{ $args{Actions} }) {
-            ( $CurrentUser, $NewAuthStat ) = $Code->(
-                Message       => $args{Message},
-                RawMessageRef => $args{RawMessageRef},
-                CurrentUser   => $CurrentUser,
-                AuthLevel     => $AuthStat,
-                Action        => $action,
-                Ticket        => $args{SystemTicket},
-                Queue         => $args{SystemQueue},
-            );
+    my %res = RT::Crypt->SignEncrypt( %args );
+    return 1 unless $res{'exit_code'};
 
-# You get the highest level of authentication you were assigned, unless you get the magic -1
-# If a module returns a "-1" then we discard the ticket, so.
-            $AuthStat = $NewAuthStat
-                if ( $NewAuthStat > $AuthStat or $NewAuthStat == -1 or $NewAuthStat == -2 );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res{'Protocol'}, Status => $res{'status'},
+    );
 
-            last if $AuthStat == -1;
-            $skip_action{$action}++ if $AuthStat == -2;
+    my @bad_recipients;
+    foreach my $line ( @status ) {
+        # if the passphrase fails, either you have a bad passphrase
+        # or gpg-agent has died.  That should get caught in Create and
+        # Update, but at least throw an error here
+        if (($line->{'Operation'}||'') eq 'PassphraseCheck'
+            && $line->{'Status'} =~ /^(?:BAD|MISSING)$/ ) {
+            $RT::Logger->error( "$line->{'Status'} PASSPHRASE: $line->{'Message'}" );
+            return 0;
         }
-
-        # strip actions we should skip
-        @{$args{Actions}} = grep !$skip_action{$_}, @{$args{Actions}}
-            if $AuthStat == -2;
-        last unless @{$args{Actions}};
-
-        last if $AuthStat == -1;
+        next unless ($line->{'Operation'}||'') eq 'RecipientsCheck';
+        next if $line->{'Status'} eq 'DONE';
+        $RT::Logger->error( $line->{'Message'} );
+        push @bad_recipients, $line;
     }
+    return 0 unless @bad_recipients;
 
-    return $AuthStat if !wantarray;
+    $_->{'AddressObj'} = (Email::Address->parse( $_->{'Recipient'} ))[0]
+        foreach @bad_recipients;
 
-    return ($AuthStat, $CurrentUser, $error);
-}
+    foreach my $recipient ( @bad_recipients ) {
+        my $status = SendEmailUsingTemplate(
+            To        => $recipient->{'AddressObj'}->address,
+            Template  => 'Error: public key',
+            Arguments => {
+                %$recipient,
+                TicketObj      => $args{'Ticket'},
+                TransactionObj => $args{'Transaction'},
+            },
+        );
+        unless ( $status ) {
+            $RT::Logger->error("Couldn't send 'Error: public key'");
+        }
+    }
 
-sub _RunUnsafeAction {
-    my %args = (
-        Action      => undef,
-        ErrorsTo    => undef,
-        Message     => undef,
-        Ticket      => undef,
-        CurrentUser => undef,
-        @_
+    my $status = SendEmailUsingTemplate(
+        To        => RT->Config->Get('OwnerEmail'),
+        Template  => 'Error to RT owner: public key',
+        Arguments => {
+            BadRecipients  => \@bad_recipients,
+            TicketObj      => $args{'Ticket'},
+            TransactionObj => $args{'Transaction'},
+        },
     );
+    unless ( $status ) {
+        $RT::Logger->error("Couldn't send 'Error to RT owner: public key'");
+    }
 
-    my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
-
-    if ( $args{'Action'} =~ /^take$/i ) {
-        my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{'CurrentUser'}->id );
-        unless ($status) {
-            MailError(
-                To          => $args{'ErrorsTo'},
-                Subject     => "Ticket not taken",
-                Explanation => $msg,
-                MIMEObj     => $args{'Message'}
-            );
-            return ( 0, "Ticket not taken, by email From: $From" );
-        }
-    } elsif ( $args{'Action'} =~ /^resolve$/i ) {
-        my $new_status = $args{'Ticket'}->FirstInactiveStatus;
-        if ($new_status) {
-            my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status);
-            unless ($status) {
+    DeleteRecipientsFromHead(
+        $args{'Entity'}->head,
+        map $_->{'AddressObj'}->address, @bad_recipients
+    );
 
-                #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, by email From: $From" );
-            }
-        }
-    } else {
-        return ( 0, "Not supported unsafe action $args{'Action'}, by email From: $From", $args{'Ticket'} );
+    unless ( $args{'Entity'}->head->get('To')
+          || $args{'Entity'}->head->get('Cc')
+          || $args{'Entity'}->head->get('Bcc') )
+    {
+        $RT::Logger->debug("$msgid No recipients that have public key, not sending");
+        return -1;
     }
-    return ( 1, "Success" );
-}
-
-=head2 _NoAuthorizedUserFound
 
-Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
-
-=cut
+    # redo without broken recipients
+    %res = RT::Crypt->SignEncrypt( %args );
+    return 0 if $res{'exit_code'};
 
-sub _NoAuthorizedUserFound {
-    my %args = (
-        Right     => undef,
-        Message   => undef,
-        Requestor => undef,
-        Queue     => undef,
-        @_
-    );
+    return 1;
+}
 
-    # Notify the RT Admin of the failure.
-    MailError(
-        To          => RT->Config->Get('OwnerEmail'),
-        Subject     => "Could not load a valid user",
-        Explanation => <<EOT,
-RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for this email (@{[$args{Requestor}]}).
+=head2 DeleteRecipientsFromHead HEAD RECIPIENTS
 
-You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
-queue @{[$args{'Queue'}]}.
+Gets a head object and list of addresses.
+Deletes addresses from To, Cc or Bcc fields.
 
-EOT
-        MIMEObj  => $args{'Message'},
-        LogLevel => 'error'
-    );
+=cut
 
-    # Also notify the requestor that his request has been dropped.
-    if ($args{'Requestor'} ne RT->Config->Get('OwnerEmail')) {
-    MailError(
-        To          => $args{'Requestor'},
-        Subject     => "Could not load a valid user",
-        Explanation => <<EOT,
-RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for your email.
+sub DeleteRecipientsFromHead {
+    my $head = shift;
+    my %skip = map { lc $_ => 1 } @_;
 
-EOT
-        MIMEObj  => $args{'Message'},
-        LogLevel => 'error'
-    );
+    foreach my $field ( qw(To Cc Bcc) ) {
+        $head->replace( $field => Encode::encode( "UTF-8",
+            join ', ', map $_->format, grep !$skip{ lc $_->address },
+                Email::Address->parse( Encode::decode( "UTF-8", $head->get( $field ) ) ) )
+        );
     }
 }
 
-=head2 _HandleMachineGeneratedMail
+=head2 EncodeToMIME
 
-Takes named params:
-    Message
-    ErrorsTo
-    Subject
+Takes a hash with a String and a Charset. Returns the string encoded
+according to RFC2047, using B (base64 based) encoding.
 
-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",
-"This message appears to be a loop (boolean)" );
+String must be a perl string, octets are returned.
+
+If Charset is not provided then $EmailOutputEncoding config option
+is used, or "latin-1" if that is not set.
 
 =cut
 
-sub _HandleMachineGeneratedMail {
-    my %args = ( Message => undef, ErrorsTo => undef, Subject => undef, MessageId => undef, @_ );
-    my $head = $args{'Message'}->head;
-    my $ErrorsTo = $args{'ErrorsTo'};
+sub EncodeToMIME {
+    my %args = (
+        String => undef,
+        Charset  => undef,
+        @_
+    );
+    my $value = $args{'String'};
+    return $value unless $value; # 0 is perfect ascii
+    my $charset  = $args{'Charset'} || RT->Config->Get('EmailOutputEncoding');
+    my $encoding = 'B';
 
-    my $IsBounce = CheckForBounce($head);
+    # using RFC2047 notation, sec 2.
+    # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
 
-    my $IsAutoGenerated = CheckForAutoGenerated($head);
+    # An 'encoded-word' may not be more than 75 characters long
+    #
+    # MIME encoding increases 4/3*(number of bytes), and always in multiples
+    # of 4. Thus we have to find the best available value of bytes available
+    # for each chunk.
+    #
+    # First we get the integer max which max*4/3 would fit on space.
+    # Then we find the greater multiple of 3 lower or equal than $max.
+    my $max = int(
+        (   ( 75 - length( '=?' . $charset . '?' . $encoding . '?' . '?=' ) )
+            * 3
+        ) / 4
+    );
+    $max = int( $max / 3 ) * 3;
 
-    my $IsSuspiciousSender = CheckForSuspiciousSender($head);
+    chomp $value;
 
-    my $IsALoop = CheckForLoops($head);
+    if ( $max <= 0 ) {
 
-    my $SquelchReplies = 0;
+        # gives an error...
+        $RT::Logger->crit("Can't encode! Charset or encoding too big.");
+        return ($value);
+    }
 
-    my $owner_mail = RT->Config->Get('OwnerEmail');
+    return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
 
-    #If the message is autogenerated, we need to know, so we can not
-    # send mail to the sender
-    if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
-        $SquelchReplies = 1;
-        $ErrorsTo       = $owner_mail;
+    $value =~ s/\s+$//;
+
+    my ( $tmp, @chunks ) = ( '', () );
+    while ( length $value ) {
+        my $char = substr( $value, 0, 1, '' );
+        my $octets = Encode::encode( $charset, $char );
+        if ( length($tmp) + length($octets) > $max ) {
+            push @chunks, $tmp;
+            $tmp = '';
+        }
+        $tmp .= $octets;
     }
+    push @chunks, $tmp if length $tmp;
 
-    # Warn someone if it's a loop, before we drop it on the ground
-    if ($IsALoop) {
-        $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
+    # encode an join chuncks
+    $value = join "\n ",
+        map MIME::Words::encode_mimeword( $_, $encoding, $charset ),
+        @chunks;
+    return ($value);
+}
 
-        #Should we mail it to RTOwner?
-        if ( RT->Config->Get('LoopsToRTOwner') ) {
-            MailError(
-                To          => $owner_mail,
-                Subject     => "RT Bounce: ".$args{'Subject'},
-                Explanation => "RT thinks this message may be a bounce",
-                MIMEObj     => $args{Message}
-            );
-        }
+sub GenMessageId {
+    my %args = (
+        Ticket      => undef,
+        Scrip       => undef,
+        ScripAction => undef,
+        @_
+    );
+    my $org = RT->Config->Get('Organization');
+    my $ticket_id = ( ref $args{'Ticket'}? $args{'Ticket'}->id : $args{'Ticket'} ) || 0;
+    my $scrip_id = ( ref $args{'Scrip'}? $args{'Scrip'}->id : $args{'Scrip'} ) || 0;
+    my $sent = ( ref $args{'ScripAction'}? $args{'ScripAction'}->{'_Message_ID'} : 0 ) || 0;
 
-        #Do we actually want to store it?
-        return ( 0, $ErrorsTo, "Message Bounced", $IsALoop )
-            unless RT->Config->Get('StoreLoops');
-    }
+    return "<rt-". $RT::VERSION ."-". $$ ."-". CORE::time() ."-". int(rand(2000)) .'.'
+        . $ticket_id ."-". $scrip_id ."-". $sent ."@". $org .">" ;
+}
 
-    # Squelch replies if necessary
-    # Don't let the user stuff the RT-Squelch-Replies-To header.
-    if ( $head->get('RT-Squelch-Replies-To') ) {
-        $head->replace(
-            'RT-Relocated-Squelch-Replies-To',
-            $head->get('RT-Squelch-Replies-To')
-        );
-        $head->delete('RT-Squelch-Replies-To');
-    }
+sub SetInReplyTo {
+    my %args = (
+        Message   => undef,
+        InReplyTo => undef,
+        Ticket    => undef,
+        @_
+    );
+    return unless $args{'Message'} && $args{'InReplyTo'};
 
-    if ($SquelchReplies) {
+    my $get_header = sub {
+        my @res;
+        if ( $args{'InReplyTo'}->isa('MIME::Entity') ) {
+            @res = map {Encode::decode("UTF-8", $_)} $args{'InReplyTo'}->head->get( shift );
+        } else {
+            @res = $args{'InReplyTo'}->GetHeader( shift ) || '';
+        }
+        return grep length, map { split /\s+/m, $_ } grep defined, @res;
+    };
 
-        # Squelch replies to the sender, and also leave a clue to
-        # allow us to squelch ALL outbound messages. This way we
-        # can punt the logic of "what to do when we get a bounce"
-        # 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->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
-        $head->replace( 'RT-DetectedAutoGenerated', 'true' );
+    my @id = $get_header->('Message-ID');
+    #XXX: custom header should begin with X- otherwise is violation of the standard
+    my @rtid = $get_header->('RT-Message-ID');
+    my @references = $get_header->('References');
+    unless ( @references ) {
+        @references = $get_header->('In-Reply-To');
     }
-    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
+    push @references, @id, @rtid;
+    if ( $args{'Ticket'} ) {
+        my $pseudo_ref = PseudoReference( $args{'Ticket'} );
+        push @references, $pseudo_ref unless grep $_ eq $pseudo_ref, @references;
+    }
+    splice @references, 4, -6
+        if @references > 10;
+
+    my $mail = $args{'Message'};
+    $mail->head->replace( 'In-Reply-To' => Encode::encode( "UTF-8", join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
+    $mail->head->replace( 'References' => Encode::encode( "UTF-8", join ' ', @references) );
 }
 
-=head2 IsCorrectAction
+sub PseudoReference {
+    my $ticket = shift;
+    return '<RT-Ticket-'. $ticket->id .'@'. RT->Config->Get('Organization') .'>';
+}
 
-Returns a list of valid actions we've found for this message
 
-=cut
+sub AddSubjectTag {
+    my $subject = shift;
+    my $ticket  = shift;
+    unless ( ref $ticket ) {
+        my $tmp = RT::Ticket->new( RT->SystemUser );
+        $tmp->Load( $ticket );
+        $ticket = $tmp;
+    }
+    my $id = $ticket->id;
+    my $queue_tag = $ticket->QueueObj->SubjectTag;
 
-sub IsCorrectAction {
-    my $action = shift;
-    my @actions = grep $_, split /-/, $action;
-    return ( 0, '(no value)' ) unless @actions;
-    foreach ( @actions ) {
-        return ( 0, $_ ) unless /^(?:comment|correspond|take|resolve)$/;
+    my $tag_re = RT->Config->Get('EmailSubjectTagRegex');
+    unless ( $tag_re ) {
+        my $tag = $queue_tag || RT->Config->Get('rtname');
+        $tag_re = qr/\Q$tag\E/;
+    } elsif ( $queue_tag ) {
+        $tag_re = qr/$tag_re|\Q$queue_tag\E/;
     }
-    return ( 1, @actions );
+    return $subject if $subject =~ /\[$tag_re\s+#$id\]/;
+
+    $subject =~ s/(\r\n|\n|\s)/ /g;
+    chomp $subject;
+    return "[". ($queue_tag || RT->Config->Get('rtname')) ." #$id] $subject";
 }
 
 sub _RecordSendEmailFailure {

commit 2a1466250f6ae44520c2752ae83bb904224fe0bf
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jan 31 16:27:10 2014 -0500

    Adjust POD headers to reflect split

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 9dc8f9f..5928333 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -68,7 +68,9 @@ use MIME::Words ();
 
 =head1 METHODS
 
-=head2 Gateway ARGSREF
+=head2 RECEIVING MAIL
+
+=head3 Gateway ARGSREF
 
 Takes parameters:
 
@@ -391,7 +393,7 @@ sub Gateway {
     return ( 1, "Success", $Ticket );
 }
 
-=head2 IsCorrectAction
+=head3 IsCorrectAction
 
 Returns a list of valid actions we've found for this message
 
@@ -434,7 +436,7 @@ sub _LoadPlugins {
     return @res;
 }
 
-=head2 GetAuthenticationLevel
+=head3 GetAuthenticationLevel
 
     # Authentication Level
     # -1 - Get out.  this user has been explicitly declined
@@ -507,7 +509,7 @@ sub GetAuthenticationLevel {
     return ($AuthStat, $CurrentUser, $error);
 }
 
-=head2 _NoAuthorizedUserFound
+=head3 _NoAuthorizedUserFound
 
 Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
 
@@ -612,7 +614,7 @@ sub CreateUser {
 }
 
 
-=head2 ParseCcAddressesFromHead HASH
+=head3 ParseCcAddressesFromHead HASH
 
 Takes a hash containing QueueObj, Head and CurrentUser objects.
 Returns a list of all email addresses in the To and Cc
@@ -642,7 +644,7 @@ sub ParseCcAddressesFromHead {
 
 
 
-=head2 ParseSenderAddressFromHead HEAD
+=head3 ParseSenderAddressFromHead HEAD
 
 Takes a MIME::Header object. Returns (user at host, friendly name, errors)
 where the first two values are the From (evaluated in order of
@@ -674,7 +676,7 @@ sub ParseSenderAddressFromHead {
     return (undef, undef, @errors);
 }
 
-=head2 ParseErrorsToAddressFromHead HEAD
+=head3 ParseErrorsToAddressFromHead HEAD
 
 Takes a MIME::Header object. Return a single value : user at host
 of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
@@ -702,7 +704,7 @@ sub ParseErrorsToAddressFromHead {
 
 
 
-=head2 ParseAddressFromHeader ADDRESS
+=head3 ParseAddressFromHeader ADDRESS
 
 Takes an address from C<$head->get('Line')> and returns a tuple: user at host, friendly name
 
@@ -723,7 +725,7 @@ sub ParseAddressFromHeader {
     return ( $AddrObj->address, $AddrObj->phrase );
 }
 
-=head2 _HandleMachineGeneratedMail
+=head3 _HandleMachineGeneratedMail
 
 Takes named params:
     Message
@@ -803,7 +805,7 @@ sub _HandleMachineGeneratedMail {
     return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
 }
 
-=head2 CheckForLoops HEAD
+=head3 CheckForLoops HEAD
 
 Takes a HEAD object of L<MIME::Head> class and returns true if the
 message's been sent by this RT instance. Uses "X-RT-Loop-Prevention"
@@ -826,7 +828,7 @@ sub CheckForLoops {
     return undef;
 }
 
-=head2 CheckForSuspiciousSender HEAD
+=head3 CheckForSuspiciousSender HEAD
 
 Takes a HEAD object of L<MIME::Head> class and returns true if sender
 is suspicious. Suspicious means mailer daemon.
@@ -864,7 +866,7 @@ sub CheckForSuspiciousSender {
     return undef;
 }
 
-=head2 CheckForAutoGenerated HEAD
+=head3 CheckForAutoGenerated HEAD
 
 Takes a HEAD object of L<MIME::Head> class and returns true if message is
 autogenerated. Checks C<Precedence>, C<Auto-Submitted>, and
@@ -1001,7 +1003,7 @@ sub _RunUnsafeAction {
     return ( 1, "Success" );
 }
 
-=head2 MailError PARAM HASH
+=head3 MailError PARAM HASH
 
 Sends an error message. Takes a param hash:
 
@@ -1085,8 +1087,9 @@ sub MailError {
     SendEmail( Entity => $entity, Bounce => 1 );
 }
 
+=head2 SENDING EMAIL
 
-=head2 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ]
+=head3 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ]
 
 Sends an email (passed as a L<MIME::Entity> object C<ENTITY>) using
 RT's outgoing mail configuration. If C<BOUNCE> is passed, and is a
@@ -1304,7 +1307,7 @@ sub SendEmail {
     return 1;
 }
 
-=head2 PrepareEmailUsingTemplate Template => '', Arguments => {}
+=head3 PrepareEmailUsingTemplate Template => '', Arguments => {}
 
 Loads a template. Parses it using arguments if it's not empty.
 Returns a tuple (L<RT::Template> object, error message).
@@ -1334,7 +1337,7 @@ sub PrepareEmailUsingTemplate {
     return $template;
 }
 
-=head2 SendEmailUsingTemplate Template => '', Arguments => {}, From => CorrespondAddress, To => '', Cc => '', Bcc => ''
+=head3 SendEmailUsingTemplate Template => '', Arguments => {}, From => CorrespondAddress, To => '', Cc => '', Bcc => ''
 
 Sends email using a template, takes name of template, arguments for it and recipients.
 
@@ -1373,7 +1376,7 @@ sub SendEmailUsingTemplate {
     return SendEmail( Entity => $mail );
 }
 
-=head2 GetForwardFrom Ticket => undef, Transaction => undef
+=head3 GetForwardFrom Ticket => undef, Transaction => undef
 
 Resolve the From field to use in forward mail
 
@@ -1393,7 +1396,7 @@ sub GetForwardFrom {
     }
 }
 
-=head2 GetForwardAttachments Ticket => undef, Transaction => undef
+=head3 GetForwardAttachments Ticket => undef, Transaction => undef
 
 Resolve the Attachments to forward
 
@@ -1449,7 +1452,7 @@ sub WillSignEncrypt {
     return wantarray ? %args : ($args{Sign} || $args{Encrypt});
 }
 
-=head2 SignEncrypt Entity => undef, Sign => 0, Encrypt => 0
+=head3 SignEncrypt Entity => undef, Sign => 0, Encrypt => 0
 
 Signs and encrypts message using L<RT::Crypt>, but as well handle errors
 with users' keys.
@@ -1555,7 +1558,7 @@ sub SignEncrypt {
     return 1;
 }
 
-=head2 DeleteRecipientsFromHead HEAD RECIPIENTS
+=head3 DeleteRecipientsFromHead HEAD RECIPIENTS
 
 Gets a head object and list of addresses.
 Deletes addresses from To, Cc or Bcc fields.
@@ -1574,7 +1577,7 @@ sub DeleteRecipientsFromHead {
     }
 }
 
-=head2 EncodeToMIME
+=head3 EncodeToMIME
 
 Takes a hash with a String and a Charset. Returns the string encoded
 according to RFC2047, using B (base64 based) encoding.
@@ -1749,7 +1752,7 @@ sub _RecordSendEmailFailure {
     }
 }
 
-=head2 ConvertHTMLToText HTML
+=head3 ConvertHTMLToText HTML
 
 Takes HTML characters and converts it to plain text characters.
 Appropriate for generating a plain text part from an HTML part of an

commit 99624a2c82a750834ad65625b5222b5c7d593e4f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Feb 6 15:09:10 2014 -0500

    Update MailPlugins to only reference existant modules

diff --git a/bin/rt-mailgate.in b/bin/rt-mailgate.in
index 7856c10..37bd250 100644
--- a/bin/rt-mailgate.in
+++ b/bin/rt-mailgate.in
@@ -46,6 +46,7 @@
 # those contributions and any derivatives thereof.
 #
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
 rt-mailgate - Mail interface to RT.
@@ -438,9 +439,9 @@ email. If you have additional filters or authentication mechanisms, you
 can list them here and they will be called in order:
 
     Set( @MailPlugins =>
-        "Filter::SpamAssassin",
-        "Auth::LDAP",
-        # ...
+        "RT::Extension::SpamAssassin",
+        "Auth::Crypt",
+        "Auth::MailFrom",
     );
 
 See the documentation for any additional plugins you have.

commit 00d085048a8a39864343ce70baade5f9e10b6679
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Feb 6 15:12:40 2014 -0500

    Move MailPlugins documentation out of rt-mailgate, where is is mostly irrelevant

diff --git a/bin/rt-mailgate.in b/bin/rt-mailgate.in
index 37bd250..9b0b8d4 100644
--- a/bin/rt-mailgate.in
+++ b/bin/rt-mailgate.in
@@ -425,73 +425,6 @@ monitoring. For instance, if you're using F</etc/aliases> and you have a
 Note that you don't have to run your RT server on your mail server, as
 the mail gateway will happily relay to a different machine.
 
-=head1 CUSTOMIZATION
-
-By default, the mail gateway will accept mail from anyone. However,
-there are situations in which you will want to authenticate users
-before allowing them to communicate with the system. You can do this
-via a plug-in mechanism in the RT configuration.
-
-You can set the array C<@MailPlugins> to be a list of plugins. The
-default plugin, if this is not given, is C<Auth::MailFrom> - that is,
-authentication of the person is done based on the C<From> header of the
-email. If you have additional filters or authentication mechanisms, you
-can list them here and they will be called in order:
-
-    Set( @MailPlugins =>
-        "RT::Extension::SpamAssassin",
-        "Auth::Crypt",
-        "Auth::MailFrom",
-    );
-
-See the documentation for any additional plugins you have.
-
-You may also put Perl subroutines into the C<@MailPlugins> array, if
-they behave as described below.
-
-=head1 WRITING PLUGINS
-
-What's actually going on in the above is that C<@MailPlugins> is a
-list of Perl modules; RT prepends C<RT::Interface::Email::> to the name,
-to form a package name, and then C<use>'s this module. The module is
-expected to provide a C<GetCurrentUser> subroutine, which takes a hash of
-several parameters:
-
-=over 4
-
-=item Message
-
-A C<MIME::Entity> object representing the email
-
-=item CurrentUser
-
-An C<RT::CurrentUser> object
-
-=item AuthStat
-
-The authentication level returned from the previous plugin.
-
-=item Ticket [OPTIONAL]
-
-The ticket under discussion
-
-=item Queue [OPTIONAL]
-
-If we don't already have a ticket id, we need to know which queue we're talking about
-
-=item Action
-
-The action being performed. At the moment, it's one of "comment" or "correspond"
-
-=back
-
-It returns two values, the new C<RT::CurrentUser> object, and the new
-authentication level. The authentication level can be zero, not allowed
-to communicate with RT at all, (a "permission denied" error is mailed to
-the correspondent) or one, which is the normal mode of operation.
-Additionally, if C<-1> is returned, then the processing of the plug-ins
-stops immediately and the message is ignored.
-
 =head1 ENVIRONMENT
 
 =over 4
diff --git a/docs/extending/mail_plugins.pod b/docs/extending/mail_plugins.pod
new file mode 100644
index 0000000..ea43e2e
--- /dev/null
+++ b/docs/extending/mail_plugins.pod
@@ -0,0 +1,67 @@
+=head1 MAIL PLUGINS
+
+By default, the mail gateway will accept mail from anyone. However,
+there are situations in which you will want to authenticate users
+before allowing them to communicate with the system. You can do this
+via a plug-in mechanism in the RT configuration.
+
+You can set the array C<@MailPlugins> to be a list of plugins. The
+default plugin, if this is not given, is C<Auth::MailFrom> - that is,
+authentication of the person is done based on the C<From> header of the
+email. If you have additional filters or authentication mechanisms, you
+can list them here and they will be called in order:
+
+    Set( @MailPlugins =>
+        "RT::Extension::SpamAssassin",
+        "Auth::Crypt",
+        "Auth::MailFrom",
+    );
+
+See the documentation for any additional plugins you have.
+
+You may also put Perl subroutines into the C<@MailPlugins> array, if
+they behave as described below.
+
+=head1 WRITING PLUGINS
+
+What's actually going on in the above is that C<@MailPlugins> is a
+list of Perl modules; RT prepends C<RT::Interface::Email::> to the name,
+to form a package name, and then C<use>'s this module. The module is
+expected to provide a C<GetCurrentUser> subroutine, which takes a hash of
+several parameters:
+
+=over 4
+
+=item Message
+
+A C<MIME::Entity> object representing the email
+
+=item CurrentUser
+
+An C<RT::CurrentUser> object
+
+=item AuthStat
+
+The authentication level returned from the previous plugin.
+
+=item Ticket [OPTIONAL]
+
+The ticket under discussion
+
+=item Queue [OPTIONAL]
+
+If we don't already have a ticket id, we need to know which queue we're talking about
+
+=item Action
+
+The action being performed. At the moment, it's one of "comment" or "correspond"
+
+=back
+
+It returns two values, the new C<RT::CurrentUser> object, and the new
+authentication level. The authentication level can be zero, not allowed
+to communicate with RT at all, (a "permission denied" error is mailed to
+the correspondent) or one, which is the normal mode of operation.
+Additionally, if C<-1> is returned, then the processing of the plug-ins
+stops immediately and the message is ignored.
+

commit 8400ff9328ce194617cc1173fc4453364d597b13
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Feb 6 18:29:05 2014 -0500

    Remove duplicate ParseCcAddressesFromHead in RT::EmailParser
    
    This code was originally in RT::Interface::Email; in e6b3fba, it was
    moved to RT::EmailParser.  However, in a90721aa it was added _back_ to
    RT::Interface::Email -- presumably because the "new mailgate" was
    derived from RT::Interface::Email just before bcb5c3ff, not the most
    current version.
    
    This mistake was slow to be undone -- a small bit was first done in
    e48d70a8.  Then dad97a8e attempted to address it on 3.2-trunk, but was
    reverted in f706a50a soas to not break the API during a stable series.
    Unfortunately, after it was re-applied to 3.3-trunk in 6c2d22842, SVK
    incorrectly put them back in the merge-up from 3.2-trunk in a6577c51.
    
    4fad7e78 got most of them, but missed ParseCcAddressesFromHead because
    of the refactorings in 19ac5467 had caused them to diverge.
    
    There are no current call sites of the RT::EmailParser implementation of
    ParseCcAddressesFromHead; remove it.  This removes the last method which
    exists in both RT::EmailParser and RT::Interface::Email.

diff --git a/lib/RT/EmailParser.pm b/lib/RT/EmailParser.pm
index b8acf89..0ca3e4b 100644
--- a/lib/RT/EmailParser.pm
+++ b/lib/RT/EmailParser.pm
@@ -278,41 +278,6 @@ sub _PostProcessNewEntity {
     RT::I18N::SetMIMEEntityToEncoding($self->{'entity'}, 'utf-8');
 }
 
-=head2 ParseCcAddressesFromHead HASHREF
-
-Takes a hashref object containing QueueObj, Head and CurrentUser objects.
-Returns a list of all email addresses in the To and Cc 
-headers b<except> the current Queue's email addresses, the CurrentUser's 
-email address and anything that the RT->Config->Get('RTAddressRegexp') matches.
-
-=cut
-
-sub ParseCcAddressesFromHead {
-    my $self = shift;
-    my %args = (
-        QueueObj    => undef,
-        CurrentUser => undef,
-        @_
-    );
-
-    my (@Addresses);
-
-    my @ToObjs = Email::Address->parse( Encode::decode( "UTF-8", $self->Head->get('To') ) );
-    my @CcObjs = Email::Address->parse( Encode::decode( "UTF-8", $self->Head->get('Cc') ) );
-
-    foreach my $AddrObj ( @ToObjs, @CcObjs ) {
-        my $Address = $AddrObj->address;
-        my $user = RT::User->new(RT->SystemUser);
-        $Address = $user->CanonicalizeEmailAddress($Address);
-        next if lc $args{'CurrentUser'}->EmailAddress eq lc $Address;
-        next if $self->IsRTAddress($Address);
-
-        push ( @Addresses, $Address );
-    }
-    return (@Addresses);
-}
-
-
 =head2 IsRTaddress ADDRESS
 
 Takes a single parameter, an email address. 

commit 7b685895762b905e3e0959b95caf9780021fe75f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Feb 6 23:38:35 2014 -0500

    Use Scope::Upper to allow returns from Gateway from nested subs
    
    Much of the complexity of the Gateway function comes from the need to
    bubble up various kinds of failures as return values of Gateway.  Using
    Scope::Upper, provide helper functions which return from Gateway
    directly, no matter how many nested calls within it the current
    evaluation is.
    
    This will also allow mail plugin logic to be simplified, while becoming
    more powerful.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 5928333..9f13a25 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -61,6 +61,7 @@ use Text::ParseWords qw/shellwords/;
 use RT::Util 'safe_run_child';
 use File::Spec;
 use MIME::Words ();
+use Scope::Upper qw/unwind HERE/;
 
 =head1 NAME
 
@@ -101,9 +102,13 @@ Returns:
 
       for succces, the status code should be 1
 
+=cut
 
+my $SCOPE;
+sub TMPFAIL { unwind (-75,     $_[0], undef, => $SCOPE) }
+sub FAILURE { unwind (  0,     $_[0], $_[1], => $SCOPE) }
+sub SUCCESS { unwind (  1, "Success", $_[0], => $SCOPE) }
 
-=cut
 
 sub Gateway {
     my $argsref = shift;
@@ -115,21 +120,20 @@ sub Gateway {
         %$argsref
     );
 
+    # Set the scope to return from with TMPFAIL/FAILURE/SUCCESS
+    $SCOPE = HERE;
+
     my $SystemTicket;
     my $Right;
 
     # Validate the action
     my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
-    unless ($status) {
-        return (
-            -75,
-            "Invalid 'action' parameter "
-                . $actions[0]
-                . " for queue "
-                . $args{'queue'},
-            undef
-        );
-    }
+    TMPFAIL(
+        "Invalid 'action' parameter "
+            . $actions[0]
+            . " for queue "
+            . $args{'queue'},
+    ) unless $status;
 
     my $parser = RT::EmailParser->new();
     $parser->SmartParseMIMEEntityFromScalar(
@@ -146,7 +150,7 @@ sub Gateway {
             Attach      => $args{'message'}
         );
 
-        return ( 0,
+        FAILURE(
             "Failed to parse this message. Something is likely badly wrong with the message"
         );
     }
@@ -188,11 +192,8 @@ sub Gateway {
         );
         next if $status > 0;
 
-        if ( $status == -2 ) {
-            return (1, $msg, undef);
-        } elsif ( $status == -1 ) {
-            return (0, $msg, undef);
-        }
+        SUCCESS($msg) if $status == -2;
+        FAILURE($msg) if $status == -1;
     }
     @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
     $parser->_DecodeBodies;
@@ -224,9 +225,8 @@ sub Gateway {
 
     # 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 );
-    }
+    FAILURE($result)
+        if $IsALoop && !$should_store_machine_generated_message;
     # }}}
 
     $args{'ticket'} ||= ExtractTicketId( $Message );
@@ -244,9 +244,8 @@ sub Gateway {
     }
 
     # We can safely have no queue of we have a known-good ticket
-    unless ( $SystemTicket->id || $SystemQueueObj->id ) {
-        return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
-    }
+    TMPFAIL("RT couldn't find the queue: " . $args{'queue'})
+        unless $SystemTicket->id || $SystemQueueObj->id;
 
     my ($AuthStat, $CurrentUser, $error) = GetAuthenticationLevel(
         MailPlugins   => \@mail_plugins,
@@ -268,9 +267,8 @@ sub Gateway {
                 Requestor => $ErrorsTo,
                 Queue     => $args{'queue'}
             );
-
         }
-        return ( 0, "Could not load a valid user", undef );
+        FAILURE("Could not load a valid user");
     }
 
     # If we got a user, but they don't have the right to say things
@@ -282,20 +280,16 @@ sub Gateway {
                 "You do not have permission to communicate with RT",
             MIMEObj => $Message
         );
-        return (
-            0,
+        FAILURE(
             ($CurrentUser->EmailAddress || $CurrentUser->Name)
             . " ($Sender) tried to submit a message to "
                 . $args{'Queue'}
                 . " without permission.",
-            undef
         );
     }
 
-
-    unless ($should_store_machine_generated_message) {
-        return ( 0, $result, undef );
-    }
+    FAILURE( $result )
+        unless $should_store_machine_generated_message;
 
     $head->replace('X-RT-Interface' => 'Email');
 
@@ -332,7 +326,7 @@ sub Gateway {
                 Explanation => $ErrStr,
                 MIMEObj     => $Message
             );
-            return ( 0, "Ticket creation From: $From failed: $ErrStr", $Ticket );
+            FAILURE("Ticket creation From: $From failed: $ErrStr", $Ticket );
         }
 
         # strip comments&corresponds from the actions we don't need
@@ -352,11 +346,11 @@ sub Gateway {
                 MIMEObj     => $Message
             );
 
-            return ( 0, $error );
+            FAILURE( $error );
         }
         $args{'ticket'} = $Ticket->id;
     } else {
-        return ( 1, "Success", $Ticket );
+        SUCCESS( $Ticket );
     }
 
     # }}}
@@ -377,20 +371,19 @@ sub Gateway {
                     Explanation => $msg,
                     MIMEObj     => $Message
                 );
-                return ( 0, "Message From: $From not recorded: $msg", $Ticket );
+                FAILURE( "Message From: $From not recorded: $msg", $Ticket );
             }
         } elsif ($unsafe_actions) {
-            my ( $status, $msg ) = _RunUnsafeAction(
+            _RunUnsafeAction(
                 Action      => $action,
                 ErrorsTo    => $ErrorsTo,
                 Message     => $Message,
                 Ticket      => $Ticket,
                 CurrentUser => $CurrentUser,
             );
-            return ($status, $msg, $Ticket) unless $status == 1;
         }
     }
-    return ( 1, "Success", $Ticket );
+    SUCCESS( $Ticket );
 }
 
 =head3 IsCorrectAction
@@ -979,7 +972,7 @@ sub _RunUnsafeAction {
                 Explanation => $msg,
                 MIMEObj     => $args{'Message'}
             );
-            return ( 0, "Ticket not taken, by email From: $From" );
+            FAILURE( "Ticket not taken, by email From: $From" );
         }
     } elsif ( $args{'Action'} =~ /^resolve$/i ) {
         my $new_status = $args{'Ticket'}->FirstInactiveStatus;
@@ -994,13 +987,12 @@ sub _RunUnsafeAction {
                     Explanation => $msg,
                     MIMEObj     => $args{'Message'}
                 );
-                return ( 0, "Ticket not resolved, by email From: $From" );
+                FAILURE( "Ticket not resolved, by email From: $From" );
             }
         }
     } else {
-        return ( 0, "Not supported unsafe action $args{'Action'}, by email From: $From", $args{'Ticket'} );
+        FAILURE( "Not supported unsafe action $args{'Action'}, by email From: $From", $args{'Ticket'} );
     }
-    return ( 1, "Success" );
 }
 
 =head3 MailError PARAM HASH
diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
index 29ea9e6..4c92b39 100644
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -245,6 +245,7 @@ Regexp::Common::net::CIDR
 Regexp::IPv6
 Role::Basic 0.12
 Scalar::Util
+Scope::Upper
 Storable 2.08
 Symbol::Global::Name 0.04
 Sys::Syslog 0.16

commit d3580131f75def77807d00a471aaf278100d2ae3
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 00:43:46 2014 -0500

    Add a base email plugin role, which provides TMPFAIL/FAILURE/SUCCESS
    
    This allows the return value of ApplyBeforeDecode-based plugins to no
    longer require magic return values, but rather use explicit directives.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 9f13a25..c37919d 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -190,10 +190,6 @@ sub Gateway {
             Queue         => $SystemQueueObj,
             Actions       => \@actions,
         );
-        next if $status > 0;
-
-        SUCCESS($msg) if $status == -2;
-        FAILURE($msg) if $status == -1;
     }
     @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
     $parser->_DecodeBodies;
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 8f8e636..26d2933 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -107,6 +107,9 @@ sub ApplyBeforeDecode { return 1 }
 use RT::Crypt;
 use RT::EmailParser ();
 
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
 sub GetCurrentUser {
     my %args = (
         Message       => undef,
@@ -139,7 +142,7 @@ sub GetCurrentUser {
                 Template  => 'Error: unencrypted message',
                 Arguments => { Message  => $args{'Message'} },
             );
-            return (-1, 'rejected because the message is unencrypted with RejectOnUnencrypted enabled');
+            FAILURE('rejected because the message is unencrypted with RejectOnUnencrypted enabled');
         }
         else {
             $args{'Message'}->head->replace(
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index d678439..38f0f58 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -51,6 +51,9 @@ package RT::Interface::Email::Auth::MailFrom;
 use strict;
 use warnings;
 
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
 use RT::Interface::Email;
 
 # This is what the ordinary, non-enhanced gateway does at the moment.
diff --git a/lib/RT/Interface/Email/Role.pm b/lib/RT/Interface/Email/Role.pm
new file mode 100644
index 0000000..2b0519d
--- /dev/null
+++ b/lib/RT/Interface/Email/Role.pm
@@ -0,0 +1,64 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at 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., 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:
+#
+# (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
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# 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::Role;
+
+use strict;
+use warnings;
+
+use Role::Basic;
+
+use RT::Interface::Email;
+
+sub TMPFAIL { RT::Interface::Email::TMPFAIL(@_) }
+sub FAILURE { RT::Interface::Email::FAILURE(@_) }
+sub SUCCESS { RT::Interface::Email::SUCCESS(@_) }
+
+sub MailError { RT::Interface::Email::MailError(@_) }
+
+1;

commit aee78696d7214e4659e1448899d5ec539826c347
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 01:13:24 2014 -0500

    Check if each named mail plugin DOES the mail plugin role

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index c37919d..8e906ab 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -412,9 +412,8 @@ sub _LoadPlugins {
             $Class->require or
                 do { $RT::Logger->error("Couldn't load $Class: $@"); next };
 
-            no strict 'refs';
-            unless ( defined *{ $Class . "::GetCurrentUser" }{CODE} ) {
-                $RT::Logger->crit( "No GetCurrentUser code found in $Class module");
+            unless ( $Class->DOES( "RT::Interface::Email::Role" ) ) {
+                $RT::Logger->crit( "$Class does not implement RT::Interface::Email::Role.  Mail plugins from RT 4.2 and earlier are not forward-compatible with RT 4.4.");
                 next;
             }
             push @res, $Class;

commit 049f74da3c9902de704135508e1c5abdfddd6003
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 01:11:15 2014 -0500

    Return values of ApplyBeforeDecode plugins' GetCurrentUser are irrelevant
    
    Remove the values passed to return() in ApplyBeforeDecode plugins'
    GetCurrentUser, which are irrelevant.  This now makes clear that errors
    decrypting or verifying do _not_ actually reject the message -- instead,
    they merely skip the decryption and verification steps.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 8e906ab..0247bd4 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -184,7 +184,7 @@ sub Gateway {
             no strict 'refs';
             *{ $class . "::GetCurrentUser" }{CODE};
         };
-        my ($status, $msg) = $Code->(
+        $Code->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
             Queue         => $SystemQueueObj,
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 26d2933..f6c56ea 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -149,7 +149,7 @@ sub GetCurrentUser {
                 'X-RT-Incoming-Encryption' => 'Not encrypted'
             );
         }
-        return 1;
+        return;
     }
 
     if ( grep {$_->{'exit_code'}} @res ) {
@@ -160,8 +160,7 @@ sub GetCurrentUser {
             $RT::Logger->warning("Failure during ".$fail->{Protocol}." ". lc($fail->{status}{Operation}) . ": ". $fail->{status}{Message});
         }
         my $reject = HandleErrors( Message => $args{'Message'}, Result => \@res );
-        return (0, 'rejected because of problems during decrypting and verifying')
-            if $reject;
+        return if $reject;
     }
 
     # attach the original encrypted message
@@ -204,8 +203,6 @@ sub GetCurrentUser {
     my %seen;
     $args{'Message'}->head->replace( 'X-RT-Privacy' => Encode::encode( "UTF-8", $_ ) )
         foreach grep !$seen{$_}++, @found;
-
-    return 1;
 }
 
 sub HandleErrors {

commit cae6a8919bc9f3fb02ce41f40eb5d98acdaefb27
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 01:15:08 2014 -0500

    Push bounce short-circuiting down into _HandleMachineGeneratedMail
    
    The only case which leads to the FAILURE directly after
    _HandleMachineGeneratedMail is if the mail is a loop and StoreLoops is
    not set.  With FAILURE(), we can now abort from within
    _HandleMachineGeneratedMail directly -- which reduces the number of
    required return values signicantly, as
    $should_store_machine_generated_message would now always be true, and
    $result is unused.
    
    It also makes clear that the later check of
    $should_store_machine_generated_message is unnecessary.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 0247bd4..34ddb21 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -210,21 +210,14 @@ sub Gateway {
     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(
+    my ($IsALoop);
+    ( $ErrorsTo, $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.
-    FAILURE($result)
-        if $IsALoop && !$should_store_machine_generated_message;
-    # }}}
-
     $args{'ticket'} ||= ExtractTicketId( $Message );
 
     # ExtractTicketId may have been overridden, and edited the Subject
@@ -284,9 +277,6 @@ sub Gateway {
         );
     }
 
-    FAILURE( $result )
-        unless $should_store_machine_generated_message;
-
     $head->replace('X-RT-Interface' => 'Email');
 
     # if plugin's updated SystemTicket then update arguments
@@ -765,8 +755,7 @@ sub _HandleMachineGeneratedMail {
         }
 
         #Do we actually want to store it?
-        return ( 0, $ErrorsTo, "Message Bounced", $IsALoop )
-            unless RT->Config->Get('StoreLoops');
+        FAILURE( "Message is a bounce" ) unless RT->Config->Get('StoreLoops');
     }
 
     # Squelch replies if necessary
@@ -790,7 +779,7 @@ sub _HandleMachineGeneratedMail {
         $head->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
         $head->replace( 'RT-DetectedAutoGenerated', 'true' );
     }
-    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
+    return ( $ErrorsTo, $IsALoop );
 }
 
 =head3 CheckForLoops HEAD

commit afe6659968c1d73d27cf483c21de3c9a62abcb14
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 01:21:12 2014 -0500

    $IsALoop is now unused in Gateway

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 34ddb21..20d272c 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -210,8 +210,7 @@ sub Gateway {
     chomp $Subject;
     
     # Lets check for mail loops of various sorts.
-    my ($IsALoop);
-    ( $ErrorsTo, $IsALoop ) = _HandleMachineGeneratedMail(
+    ( $ErrorsTo ) = _HandleMachineGeneratedMail(
         Message  => $Message,
         ErrorsTo => $ErrorsTo,
         Subject  => $Subject,
@@ -779,7 +778,7 @@ sub _HandleMachineGeneratedMail {
         $head->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
         $head->replace( 'RT-DetectedAutoGenerated', 'true' );
     }
-    return ( $ErrorsTo, $IsALoop );
+    return ( $ErrorsTo );
 }
 
 =head3 CheckForLoops HEAD

commit dc1f8fea69a40ecf519713acf29586af2a0a22d3
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 7 19:48:02 2014 -0500

    Stop moving RT-Squelch-Replies-To aside
    
    There's not much reason to preserve this internal value if it shows up
    on incoming messages.  Other RT-internal headers
    (X-RT-Incoming-Encryption, etc) are simply stripped with no replacement;
    do the same here.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 20d272c..c8ac53f 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -759,13 +759,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') ) {
-        $head->replace(
-            'RT-Relocated-Squelch-Replies-To',
-            $head->get('RT-Squelch-Replies-To')
-        );
-        $head->delete('RT-Squelch-Replies-To');
-    }
+    $head->delete('RT-Squelch-Replies-To');
 
     if ($SquelchReplies) {
 

commit 03979a3ec80070f270da3f8171e89639847415e0
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon Mar 10 18:43:27 2014 -0400

    Simplify _HandleMachineGeneratedMail logic
    
    The only incidental difference is that the message included in a "RT
    think this may be a bounce" mail will have any RT-Squelch-Replies-To
    headers removed, which seems an insignificant price to pay for the
    simpler logic.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index c8ac53f..cca33d7 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -197,7 +197,6 @@ sub Gateway {
     $parser->_PostProcessNewEntity;
 
     my $head = $Message->head;
-    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
     my $Sender = (ParseSenderAddressFromHead( $head ))[0];
     my $From = Encode::decode( "UTF-8", $head->get("From") );
     chomp $From if defined $From;
@@ -208,14 +207,16 @@ sub Gateway {
     #Pull apart the subject line
     my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || '');
     chomp $Subject;
-    
+
     # Lets check for mail loops of various sorts.
-    ( $ErrorsTo ) = _HandleMachineGeneratedMail(
-        Message  => $Message,
-        ErrorsTo => $ErrorsTo,
-        Subject  => $Subject,
-        MessageId => $MessageId
-    );
+    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
+    $ErrorsTo = RT->Config->Get('OwnerEmail')
+        if IsMachineGeneratedMail(
+            Message   => $Message,
+            ErrorsTo  => $ErrorsTo,
+            Subject   => $Subject,
+            MessageId => $MessageId,
+        );
 
     $args{'ticket'} ||= ExtractTicketId( $Message );
 
@@ -715,29 +716,28 @@ Returns a triple of ("Should we continue (boolean)", "New value for $ErrorsTo",
 
 =cut
 
-sub _HandleMachineGeneratedMail {
-    my %args = ( Message => undef, ErrorsTo => undef, Subject => undef, MessageId => undef, @_ );
+sub IsMachineGeneratedMail {
+    my %args = (
+        Message => undef,
+        Subject => undef,
+        MessageId => undef,
+        @_
+    );
     my $head = $args{'Message'}->head;
-    my $ErrorsTo = $args{'ErrorsTo'};
 
     my $IsBounce = CheckForBounce($head);
-
     my $IsAutoGenerated = CheckForAutoGenerated($head);
-
     my $IsSuspiciousSender = CheckForSuspiciousSender($head);
-
     my $IsALoop = CheckForLoops($head);
 
-    my $SquelchReplies = 0;
-
     my $owner_mail = RT->Config->Get('OwnerEmail');
 
-    #If the message is autogenerated, we need to know, so we can not
+    # Don't let the user stuff the RT-Squelch-Replies-To header.
+    $head->delete('RT-Squelch-Replies-To');
+
+    # If the message is autogenerated, we need to know, so we can not
     # send mail to the sender
-    if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
-        $SquelchReplies = 1;
-        $ErrorsTo       = $owner_mail;
-    }
+    return unless $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop;
 
     # Warn someone if it's a loop, before we drop it on the ground
     if ($IsALoop) {
@@ -757,22 +757,16 @@ sub _HandleMachineGeneratedMail {
         FAILURE( "Message is a bounce" ) unless RT->Config->Get('StoreLoops');
     }
 
-    # Squelch replies if necessary
-    # Don't let the user stuff the RT-Squelch-Replies-To header.
-    $head->delete('RT-Squelch-Replies-To');
-
-    if ($SquelchReplies) {
+    # Squelch replies to the sender, and also leave a clue to
+    # allow us to squelch ALL outbound messages. This way we
+    # can punt the logic of "what to do when we get a bounce"
+    # 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->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
+    $head->replace( 'RT-DetectedAutoGenerated', 'true' );
 
-        # Squelch replies to the sender, and also leave a clue to
-        # allow us to squelch ALL outbound messages. This way we
-        # can punt the logic of "what to do when we get a bounce"
-        # 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->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
-        $head->replace( 'RT-DetectedAutoGenerated', 'true' );
-    }
-    return ( $ErrorsTo );
+    return 1;
 }
 
 =head3 CheckForLoops HEAD

commit 1a1d88f59e6e6f7774ac87053fb0e5f93f658a6f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 01:14:18 2014 -0500

    Use different method names rather than an ApplyBeforeDecode method
    
    Use ->can to determine what kind of action a mail plugin does, rather
    than having to define a separate subroutine which changes the meaning of
    GetCurrentUser.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index cca33d7..0fed1b7 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -163,27 +163,10 @@ sub Gateway {
     my $SystemQueueObj = RT::Queue->new( RT->SystemUser );
     $SystemQueueObj->Load( $args{'queue'} );
 
-    my %skip_plugin;
-    foreach my $class( grep !ref, @mail_plugins ) {
+    foreach my $class ( grep !ref, @mail_plugins ) {
         # check if we should apply filter before decoding
-        my $check_cb = do {
-            no strict 'refs';
-            *{ $class . "::ApplyBeforeDecode" }{CODE};
-        };
-        next unless defined $check_cb;
-        next unless $check_cb->(
-            Message       => $Message,
-            RawMessageRef => \$args{'message'},
-            Queue         => $SystemQueueObj,
-            Actions       => \@actions,
-        );
-
-        $skip_plugin{ $class }++;
-
-        my $Code = do {
-            no strict 'refs';
-            *{ $class . "::GetCurrentUser" }{CODE};
-        };
+        my $Code = $class->can("BeforeDecode");
+        next unless $Code;
         $Code->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
@@ -191,7 +174,7 @@ sub Gateway {
             Actions       => \@actions,
         );
     }
-    @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
+
     $parser->_DecodeBodies;
     $parser->RescueOutlook;
     $parser->_PostProcessNewEntity;
@@ -450,8 +433,8 @@ sub GetAuthenticationLevel {
         if ( ref($_) eq "CODE" ) {
             $Code = $_;
         } else {
-            no strict 'refs';
-            $Code = *{ $_ . "::GetCurrentUser" }{CODE};
+            $Code = $_->can("GetCurrentUser");
+            next unless $Code;
         }
 
         foreach my $action (@{ $args{Actions} }) {
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index f6c56ea..539d31f 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -102,15 +102,13 @@ Read also: L<RT::Crypt> and L<RT::Crypt::SMIME>.
 
 =cut
 
-sub ApplyBeforeDecode { return 1 }
-
 use RT::Crypt;
 use RT::EmailParser ();
 
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
-sub GetCurrentUser {
+sub BeforeDecode {
     my %args = (
         Message       => undef,
         RawMessageRef => undef,

commit 202a69f978a5e156c327820d2b3e5786b3bd002d
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 01:53:58 2014 -0500

    Use FAILURE to abort from GetCurrentUser, rather than a magic -1 value

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 0fed1b7..2392037 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -229,17 +229,15 @@ sub Gateway {
     );
 
     # If authentication fails and no new user was created, get out.
-    if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
+    if ( !$CurrentUser || !$CurrentUser->id ) {
 
         # If the plugins refused to create one, they lose.
-        unless ( $AuthStat == -1 ) {
-            _NoAuthorizedUserFound(
-                Right     => $Right,
-                Message   => $Message,
-                Requestor => $ErrorsTo,
-                Queue     => $args{'queue'}
-            );
-        }
+        _NoAuthorizedUserFound(
+            Right     => $Right,
+            Message   => $Message,
+            Requestor => $ErrorsTo,
+            Queue     => $args{'queue'}
+        );
         FAILURE("Could not load a valid user");
     }
 
@@ -451,9 +449,8 @@ sub GetAuthenticationLevel {
 # You get the highest level of authentication you were assigned, unless you get the magic -1
 # If a module returns a "-1" then we discard the ticket, so.
             $AuthStat = $NewAuthStat
-                if ( $NewAuthStat > $AuthStat or $NewAuthStat == -1 or $NewAuthStat == -2 );
+                if ( $NewAuthStat > $AuthStat or $NewAuthStat == -2 );
 
-            last if $AuthStat == -1;
             $skip_action{$action}++ if $AuthStat == -2;
         }
 
@@ -461,8 +458,6 @@ sub GetAuthenticationLevel {
         @{$args{Actions}} = grep !$skip_action{$_}, @{$args{Actions}}
             if $AuthStat == -2;
         last unless @{$args{Actions}};
-
-        last if $AuthStat == -1;
     }
 
     return $AuthStat if !wantarray;
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 38f0f58..d055486 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -75,7 +75,7 @@ sub GetCurrentUser {
 
     unless ( $Address ) {
         $RT::Logger->error("Couldn't parse or find sender's address");
-        return ( $args{'CurrentUser'}, -1 );
+        FAILURE("Couldn't parse or find sender's address");
     }
 
     my $CurrentUser = RT::CurrentUser->new;
@@ -90,14 +90,14 @@ sub GetCurrentUser {
     my $unpriv = RT->UnprivilegedUsers();
     unless ( $unpriv->Id ) {
         $RT::Logger->crit("Couldn't find the 'Unprivileged' internal group");
-        return ( $args{'CurrentUser'}, -1 );
+        FAILURE("Couldn't find the 'Unprivileged' internal group");
     }
 
     my $everyone = RT::Group->new( RT->SystemUser );
     $everyone->LoadSystemInternalGroup('Everyone');
     unless ( $everyone->Id ) {
         $RT::Logger->crit("Couldn't find the 'Everyone' internal group");
-        return ( $args{'CurrentUser'}, -1 );
+        FAILURE("Couldn't find the 'Everyone' internal group");
     }
 
     $RT::Logger->debug("Going to create user with address '$Address'" );

commit 19f7ec50a2ac095b697fbb8de2e66b4cfe4e0fac
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 01:54:14 2014 -0500

    Remove the unused $error variable

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 2392037..68715a7 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -416,7 +416,7 @@ sub GetAuthenticationLevel {
         @_,
     );
 
-    my ( $CurrentUser, $AuthStat, $error );
+    my ( $CurrentUser, $AuthStat );
 
     # Initalize AuthStat so comparisons work correctly
     $AuthStat = -9999999;
@@ -462,7 +462,7 @@ sub GetAuthenticationLevel {
 
     return $AuthStat if !wantarray;
 
-    return ($AuthStat, $CurrentUser, $error);
+    return ($AuthStat, $CurrentUser);
 }
 
 =head3 _NoAuthorizedUserFound

commit 332f4d2b7eb6ded846862b013be868eb7f53e4d4
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Mar 11 15:15:41 2014 -0400

    Move $Right to where it is used

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 68715a7..144fc36 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -124,7 +124,6 @@ sub Gateway {
     $SCOPE = HERE;
 
     my $SystemTicket;
-    my $Right;
 
     # Validate the action
     my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
@@ -209,6 +208,7 @@ sub Gateway {
 
     $SystemTicket = RT::Ticket->new( RT->SystemUser );
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
+    my $Right;
     if ( $SystemTicket->id ) {
         $Right = 'ReplyToTicket';
     } else {

commit a418722c92f31047e6f24a6efa3dc6e5a480302a
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 12 02:00:23 2014 -0500

    Move SystemTicket definition to where it is first used

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 144fc36..722b052 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -123,8 +123,6 @@ sub Gateway {
     # Set the scope to return from with TMPFAIL/FAILURE/SUCCESS
     $SCOPE = HERE;
 
-    my $SystemTicket;
-
     # Validate the action
     my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
     TMPFAIL(
@@ -206,7 +204,7 @@ sub Gateway {
     my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
     chomp $NewSubject;
 
-    $SystemTicket = RT::Ticket->new( RT->SystemUser );
+    my $SystemTicket = RT::Ticket->new( RT->SystemUser );
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
     my $Right;
     if ( $SystemTicket->id ) {

commit 8ae942ffef8812dec4ee448eec3eb2e4d8bb480b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 12 02:00:43 2014 -0500

    Move NewSubject to where it is used

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 722b052..7713d75 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -200,10 +200,6 @@ sub Gateway {
 
     $args{'ticket'} ||= ExtractTicketId( $Message );
 
-    # ExtractTicketId may have been overridden, and edited the Subject
-    my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
-    chomp $NewSubject;
-
     my $SystemTicket = RT::Ticket->new( RT->SystemUser );
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
     my $Right;
@@ -277,6 +273,10 @@ sub Gateway {
             );
         }
 
+        # ExtractTicketId may have been overridden, and edited the Subject
+        my $NewSubject = Encode::decode( "UTF-8", $head->get('Subject') );
+        chomp $NewSubject;
+
         my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
             Queue     => $SystemQueueObj->Id,
             Subject   => $NewSubject,

commit 24bf62a57013d15a35d21816eb91497344173033
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 7 16:17:10 2014 -0500

    Remove CreateUser, merging to form a more featureful LoadOrCreateByEmail
    
    CreateUser is a near-duplicate of LoadOrCreateByEmail -- which is not
    surprising, since they share history.  The code originally arose in the
    RT::Interface::Email in f51a612f, and was copied into the "add a
    watcher" code in Tickets and Queues in 29fbc05f and 40b1ea0,
    respectively.  The code in Ticket became LoadOrCreateByEmail in
    bd37466e, but the code in Queue remained there until 97a6962a.
    
    This removes the last duplication of this code, merging the additional
    features from CreateUser into LoadOrCreateByEmail.  This also allows
    RT::Crypt::SMIME to create users with a more correct comment string than
    "Autocreated when added as a watcher", as it previously did.

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 38ecd85..c513b4c 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -474,8 +474,13 @@ sub Verify {
             return %res;
         }
 
+        my ($address) = Email::Address->parse($signer->{User}[0]{String});
         my $user = RT::User->new( $RT::SystemUser );
-        $user->LoadOrCreateByEmail( $signer->{User}[0]{String} );
+        $user->LoadOrCreateByEmail(
+            EmailAddress => $address->address,
+            RealName     => $address->phrase,
+            Comments     => 'Autocreated during SMIME parsing',
+        );
         my $current_key = $user->SMIMECertificate;
         last if $current_key && $current_key eq $key;
 
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 7713d75..52fcb81 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -510,64 +510,6 @@ EOT
     }
 }
 
-sub CreateUser {
-    my ( $Username, $Address, $Name, $ErrorsTo, $entity ) = @_;
-
-    my $NewUser = RT::User->new( RT->SystemUser );
-
-    my ( $Val, $Message ) = $NewUser->Create(
-        Name => ( $Username || $Address ),
-        EmailAddress => $Address,
-        RealName     => $Name,
-        Password     => undef,
-        Privileged   => 0,
-        Comments     => 'Autocreated on ticket submission',
-    );
-
-    unless ($Val) {
-
-        # Deal with the race condition of two account creations at once
-        if ($Username) {
-            $NewUser->LoadByName($Username);
-        }
-
-        unless ( $NewUser->Id ) {
-            $NewUser->LoadByEmail($Address);
-        }
-
-        unless ( $NewUser->Id ) {
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "User could not be created",
-                Explanation =>
-                    "User creation failed in mailgateway: $Message",
-                MIMEObj  => $entity,
-                LogLevel => 'crit',
-            );
-        }
-    }
-
-    #Load the new user object
-    my $CurrentUser = RT::CurrentUser->new;
-    $CurrentUser->LoadByEmail( $Address );
-
-    unless ( $CurrentUser->id ) {
-        $RT::Logger->warning(
-            "Couldn't load user '$Address'." . "giving up" );
-        MailError(
-            To          => $ErrorsTo,
-            Subject     => "User could not be loaded",
-            Explanation =>
-                "User  '$Address' could not be loaded in the mail gateway",
-            MIMEObj  => $entity,
-            LogLevel => 'crit'
-        );
-    }
-
-    return $CurrentUser;
-}
-
-
 =head3 ParseCcAddressesFromHead HASH
 
 Takes a hash containing QueueObj, Head and CurrentUser objects.
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index d055486..9e93f57 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -178,10 +178,26 @@ sub GetCurrentUser {
         }
     }
 
-    $CurrentUser = RT::Interface::Email::CreateUser(
-        undef, $Address, $Name, $Address, $args{'Message'}
+    my $user = RT::User->new( RT->SystemUser );
+    $user->LoadOrCreateByEmail(
+        EmailAddress => $Address,
+        RealName     => $Name,
+        Comments     => 'Autocreated on ticket submission',
     );
 
+    $CurrentUser->LoadByEmail( $Address );
+
+    unless ( $CurrentUser->id ) {
+        MailError(
+            To          => $Address,
+            Subject     => "User could not be loaded",
+            Explanation =>
+                "User  '$Address' could not be loaded in the mail gateway",
+            MIMEObj  => $args{'Message'},
+            LogLevel => 'crit'
+        );
+    }
+
     return ( $CurrentUser, 1 );
 }
 
diff --git a/lib/RT/User.pm b/lib/RT/User.pm
index 2ad0a14..9262860 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -517,45 +517,44 @@ Returns a tuple of the user's id and a status message.
 
 sub LoadOrCreateByEmail {
     my $self = shift;
-    my $email = shift;
 
-    my ($message, $name);
-    if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
-        ($email, $name) = ($email->address, $email->phrase);
+    my %create;
+    if (@_ > 1) {
+        %create = (@_);
+    } elsif ( UNIVERSAL::isa( $_[0] => 'Email::Address' ) ) {
+        @create{'EmailAddress','RealName'} = ($_[0]->address, $_[0]->phrase);
     } else {
-        ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
+        @create{'EmailAddress','RealName'} = RT::Interface::Email::ParseAddressFromHeader( $_[0] );
     }
 
-    $self->LoadByEmail( $email );
-    $self->Load( $email ) unless $self->Id;
-    $message = $self->loc('User loaded');
+    $self->LoadByEmail( $create{EmailAddress} );
+    $self->Load( $create{EmailAddress} ) unless $self->Id;
 
-    unless( $self->Id ) {
-        my $val;
-        ($val, $message) = $self->Create(
-            Name         => $email,
-            EmailAddress => $email,
-            RealName     => $name,
-            Privileged   => 0,
-            Comments     => 'Autocreated when added as a watcher',
-        );
-        unless ( $val ) {
-            # Deal with the race condition of two account creations at once
-            $self->LoadByEmail( $email );
-            unless ( $self->Id ) {
-                sleep 5;
-                $self->LoadByEmail( $email );
-            }
-            if ( $self->Id ) {
-                $RT::Logger->error("Recovered from creation failure due to race condition");
-                $message = $self->loc("User loaded");
-            } else {
-                $RT::Logger->crit("Failed to create user ". $email .": " .$message);
-            }
-        }
+    return wantarray ? ($self->Id, $self->loc("User loaded")) : $self->Id
+        if $self->Id;
+
+    $create{Name}       ||= $create{EmailAddress};
+    $create{Privileged} ||= 0;
+    $create{Comments}   //= 'Autocreated when added as a watcher';
+
+    my ($val, $message) = $self->Create( %create );
+    return wantarray ? ($self->Id, $self->loc("User loaded")) : $self->Id
+        if $self->Id;
+
+    # Deal with the race condition of two account creations at once
+    $self->LoadByEmail( $create{EmailAddress} );
+    unless ( $self->Id ) {
+        sleep 5;
+        $self->LoadByEmail( $create{EmailAddress} );
+    }
+
+    if ( $self->Id ) {
+        $RT::Logger->error("Recovered from creation failure due to race condition");
+        return wantarray ? ($self->Id, $self->loc("User loaded")) : $self->Id;
+    } else {
+        $RT::Logger->crit("Failed to create user $create{EmailAddress}: $message");
+        return wantarray ? (0, $message) : 0 unless $self->id;
     }
-    return wantarray ? (0, $message) : 0 unless $self->id;
-    return wantarray ? ($self->Id, $message) : $self->Id;
 }
 
 =head2 ValidateEmailAddress ADDRESS

commit 6dd0a2922889fb050af0d699f406d92447268b0c
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 12 00:29:07 2014 -0500

    Always create the user; this simplifies ACL checking greatly
    
    This is a behavior change.  Previously, if nonexistant at example.com sent
    mail to a queue which they did not have rights to create a ticket, no
    user would be created.  Now, the user is fully created -- this allows
    them to, for example, possibly pick up more rights than just Everyone
    and Unprivileged -- before ticket creation is attempted.
    
    This simplifies ACL checking greatly, as the GetCurrentUser code no
    longer needs to wave its hands about Everyone and Unprivileged, and
    attempt to check rights before it actually has a CurrentUser.  It
    additionally allows users to be created with additional rights (pulled,
    say, from LDAP).
    
    In deployments which do not grant CreateTicket to Everyone or
    Unprivileged, this will result in more unprivileged users.  However, the
    combination of deployments that accept arbitrary mail from the outside
    world, as well as not granting CreateTicket, is rather rare -- and in
    fact, an email is sent to OwnerEmail every time those constraints were
    satisfied.  Under the assumption that any volume of such mails would be
    untenable in production, the chance of such configurations existing in
    the wild seems even lower.  As such, the tiny cost of additional
    Unprivileged users in such cases is worth the notable code
    simplification.

diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
index 1ffd261..8eec78f 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -30,6 +30,13 @@ reporting.
 
 RT::Interface::Email no longer exports functions.
 
+=item *
+
+Incoming email now always creates users for the C<From:> address.
+Previously, if the C<CreateTicket> right was not granted to C<Everyone>
+or C<Unprivileged>, the email would be rejected without a user; now, the
+user to be created even when the mail is rejected.
+
 =back
 
 =cut
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 52fcb81..82766c6 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -244,10 +244,8 @@ sub Gateway {
                 "You do not have permission to communicate with RT",
             MIMEObj => $Message
         );
-        FAILURE(
-            ($CurrentUser->EmailAddress || $CurrentUser->Name)
-            . " ($Sender) tried to submit a message to "
-                . $args{'Queue'}
+        FAILURE("$Sender tried to submit a message to "
+                . $args{'queue'}
                 . " without permission.",
         );
     }
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 9e93f57..05027d6 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -86,80 +86,59 @@ sub GetCurrentUser {
         return ( $CurrentUser, 1 );
     }
 
-    # If the user can't be loaded, we may need to create one. Figure out the acl situation.
-    my $unpriv = RT->UnprivilegedUsers();
-    unless ( $unpriv->Id ) {
-        $RT::Logger->crit("Couldn't find the 'Unprivileged' internal group");
-        FAILURE("Couldn't find the 'Unprivileged' internal group");
-    }
 
-    my $everyone = RT::Group->new( RT->SystemUser );
-    $everyone->LoadSystemInternalGroup('Everyone');
-    unless ( $everyone->Id ) {
-        $RT::Logger->crit("Couldn't find the 'Everyone' internal group");
-        FAILURE("Couldn't find the 'Everyone' internal group");
-    }
+    my $user = RT::User->new( RT->SystemUser );
+    $user->LoadOrCreateByEmail(
+        RealName     => $Name,
+        EmailAddress => $Address,
+        Comments     => 'Autocreated on ticket submission',
+    );
 
-    $RT::Logger->debug("Going to create user with address '$Address'" );
+    $CurrentUser = RT::CurrentUser->new;
+    $CurrentUser->Load( $user->id );
+
+    return (undef, 0) unless $CurrentUser->id;
 
-    # but before we do that, we need to make sure that the created user would have the right
-    # to do what we're doing.
     if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
         my $qname = $args{'Queue'}->Name;
         # We have a ticket. that means we're commenting or corresponding
         if ( $args{'Action'} =~ /^comment$/i ) {
 
-            # check to see whether "Everyone" or "Unprivileged users" can comment on tickets
-            unless ( $everyone->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                        Right => 'CommentOnTicket' )
-                     || $unpriv->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                         Right => 'CommentOnTicket' ) )
-            {
+            # check to see whether if they can comment on the ticket
+            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to comment on ticket in queue '$qname'");
-                return ( $args{'CurrentUser'}, 0 );
+                return ( $CurrentUser, 0 );
             }
         }
         elsif ( $args{'Action'} =~ /^correspond$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $everyone->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                        Right  => 'ReplyToTicket' )
-                     || $unpriv->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                         Right  => 'ReplyToTicket' ) )
-            {
+            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to reply to ticket in queue '$qname'");
-                return ( $args{'CurrentUser'}, 0 );
+                return ( $CurrentUser, 0 );
             }
         }
         elsif ( $args{'Action'} =~ /^take$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $everyone->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                        Right  => 'OwnTicket' )
-                     || $unpriv->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                         Right  => 'OwnTicket' ) )
-            {
+            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to own ticket in queue '$qname'");
-                return ( $args{'CurrentUser'}, 0 );
+                return ( $CurrentUser, 0 );
             }
 
         }
         elsif ( $args{'Action'} =~ /^resolve$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $everyone->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                        Right  => 'ModifyTicket' )
-                     || $unpriv->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                         Right  => 'ModifyTicket' ) )
-            {
+            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to resolve ticket in queue '$qname'");
-                return ( $args{'CurrentUser'}, 0 );
+                return ( $CurrentUser, 0 );
             }
 
         }
         else {
             $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
-            return ( $args{'CurrentUser'}, 0 );
+            return ( $CurrentUser, 0 );
         }
     }
 
@@ -168,36 +147,12 @@ sub GetCurrentUser {
         my $qname = $args{'Queue'}->Name;
 
         # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
-        unless ( $everyone->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                    Right  => 'CreateTicket' )
-                 || $unpriv->PrincipalObj->HasRight( Object => $args{'Queue'},
-                                                     Right  => 'CreateTicket' ) )
-        {
+        unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' ) ) {
             $RT::Logger->debug("Unprivileged users have no right to create ticket in queue '$qname'");
-            return ( $args{'CurrentUser'}, 0 );
+            return ( $CurrentUser, 0 );
         }
     }
 
-    my $user = RT::User->new( RT->SystemUser );
-    $user->LoadOrCreateByEmail(
-        EmailAddress => $Address,
-        RealName     => $Name,
-        Comments     => 'Autocreated on ticket submission',
-    );
-
-    $CurrentUser->LoadByEmail( $Address );
-
-    unless ( $CurrentUser->id ) {
-        MailError(
-            To          => $Address,
-            Subject     => "User could not be loaded",
-            Explanation =>
-                "User  '$Address' could not be loaded in the mail gateway",
-            MIMEObj  => $args{'Message'},
-            LogLevel => 'crit'
-        );
-    }
-
     return ( $CurrentUser, 1 );
 }
 
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index 89b1b60..89bfcee 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 
-use RT::Test config => 'Set( $UnsafeEmailCommands, 1);', tests => 228, actual_server => 1;
+use RT::Test config => 'Set( $UnsafeEmailCommands, 1);', tests => undef, actual_server => 1;
 my ($baseurl, $m) = RT::Test->started_ok;
 
 use RT::Tickets;
@@ -192,14 +192,11 @@ EOF
 
     my $u = RT::User->new(RT->SystemUser);
     $u->Load("doesnotexist\@@{[RT->Config->Get('rtname')]}");
-    ok( !$u->Id, "user does not exist and was not created by failed ticket submission");
+    ok( $u->Id, "user was created by failed ticket submission");
 
-    $m->next_warning_like(qr/RT's configuration does not allow\s+for the creation of a new user for this email/);
-    $m->next_warning_like(qr/RT could not load a valid user/);
-    TODO: {
-        local $TODO = "we're a bit noisy for this warning case";
-        $m->no_leftover_warnings_ok;
-    }
+    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist\@\S+ tried to submit a message to General without permission/);
+    $m->no_leftover_warnings_ok;
 }
 
 diag "grant everybody with CreateTicket right";
@@ -234,7 +231,7 @@ EOF
 
     my $u = RT::User->new( RT->SystemUser );
     $u->Load( "doesnotexist\@@{[RT->Config->Get('rtname')]}" );
-    ok ($u->Id, "user does not exist and was created by ticket submission");
+    ok ($u->Id, "user does exist and was created by ticket submission");
     $ticket_id = $id;
     $m->no_warnings_ok;
 }
@@ -255,12 +252,10 @@ EOF
 
     my $u = RT::User->new(RT->SystemUser);
     $u->Load('doesnotexist-2@'.RT->Config->Get('rtname'));
-    ok( !$u->Id, " user does not exist and was not created by ticket correspondence submission");
-    $m->next_warning_like(qr/RT's configuration does not allow\s+for the creation of a new user for this email \(doesnotexist-2\@example\.com\)/);
-    TODO: {
-        local $TODO = "we're a bit noisy for this warning case";
-        $m->no_leftover_warnings_ok;
-    }
+    ok( $u->Id, "user was created by ticket correspondence submission");
+    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist-2\@\S+ tried to submit a message to General without permission/);
+    $m->no_leftover_warnings_ok;
 }
 
 diag "grant everyone 'ReplyToTicket' right";
@@ -341,12 +336,10 @@ EOF
 
     my $u = RT::User->new(RT->SystemUser);
     $u->Load('doesnotexist-3@'.RT->Config->Get('rtname'));
-    ok( !$u->Id, " user does not exist and was not created by ticket comment submission");
-    $m->next_warning_like(qr/RT's configuration does not allow\s+for the creation of a new user for this email \(doesnotexist-3\@example\.com\)/);
-    TODO: {
-        local $TODO = "we're a bit noisy for this warning case";
-        $m->no_leftover_warnings_ok;
-    }
+    ok( $u->Id, "user was created by ticket comment submission");
+    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist-3\@\S+ tried to submit a message to General without permission/);
+    $m->no_leftover_warnings_ok;
 }
 
 
@@ -826,3 +819,5 @@ $m->no_warnings_ok;
 
 };
 
+undef $m;
+done_testing;

commit 39d8b60b3b7afad5e4a40cad9c41148a4421477a
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 12 00:46:27 2014 -0500

    Split authentication from authorization
    
    This allows the large GetCurrentUser subroutine to be split into two
    parts: determining who the user is firstly, and then secondly
    determining if they have permissions.  This causes existing users to
    follow the same checks that non-existant ones do, normalizing the
    warning messages; while somewhat worse now, they will be improved in
    subsequent commits.

diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
index 8eec78f..7570c28 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -37,6 +37,11 @@ Previously, if the C<CreateTicket> right was not granted to C<Everyone>
 or C<Unprivileged>, the email would be rejected without a user; now, the
 user to be created even when the mail is rejected.
 
+=item *
+The L<RT_Config/@MailPlugins> functionality has been rewritten; mail
+plugins written for previous versions of RT will not function as
+expected.  See F<docs/extending/mail_plugins.pod>
+
 =back
 
 =cut
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 82766c6..e9d01cc 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -213,27 +213,23 @@ sub Gateway {
     TMPFAIL("RT couldn't find the queue: " . $args{'queue'})
         unless $SystemTicket->id || $SystemQueueObj->id;
 
-    my ($AuthStat, $CurrentUser, $error) = GetAuthenticationLevel(
+    my $CurrentUser = GetCurrentUser(
+        ErrorsTo      => $ErrorsTo,
         MailPlugins   => \@mail_plugins,
-        Actions       => \@actions,
         Message       => $Message,
         RawMessageRef => \$args{message},
-        SystemTicket  => $SystemTicket,
-        SystemQueue   => $SystemQueueObj,
+        Ticket        => $SystemTicket,
+        Queue         => $SystemQueueObj,
     );
 
-    # If authentication fails and no new user was created, get out.
-    if ( !$CurrentUser || !$CurrentUser->id ) {
-
-        # If the plugins refused to create one, they lose.
-        _NoAuthorizedUserFound(
-            Right     => $Right,
-            Message   => $Message,
-            Requestor => $ErrorsTo,
-            Queue     => $args{'queue'}
-        );
-        FAILURE("Could not load a valid user");
-    }
+    my $AuthStat = CheckACL(
+        MailPlugins   => \@mail_plugins,
+        Actions       => \@actions,
+        Message       => $Message,
+        CurrentUser   => $CurrentUser,
+        Ticket        => $SystemTicket,
+        Queue         => $SystemQueueObj,
+    );
 
     # If we got a user, but they don't have the right to say things
     if ( $AuthStat == 0 ) {
@@ -391,7 +387,53 @@ sub _LoadPlugins {
     return @res;
 }
 
-=head3 GetAuthenticationLevel
+=head3 GetCurrentUser
+
+=cut
+
+sub GetCurrentUser {
+    my %args = (
+        ErrorsTo      => undef,
+        MailPlugins   => [],
+        Message       => undef,
+        RawMessageRef => undef,
+        Ticket        => undef,
+        Queue         => undef,
+        @_,
+    );
+
+    # Since this needs loading, no matter what
+    foreach (@{ $args{MailPlugins} }) {
+        my $Code;
+        if ( ref($_) eq "CODE" ) {
+            $Code = $_;
+        } else {
+            $Code = $_->can("GetCurrentUser");
+            next unless $Code;
+        }
+
+        my $CurrentUser = $Code->(
+            Message       => $args{Message},
+            RawMessageRef => $args{RawMessageRef},
+            Ticket        => $args{Ticket},
+            Queue         => $args{Queue},
+        );
+        return $CurrentUser if $CurrentUser and $CurrentUser->id;
+    }
+
+    # None of the GetCurrentUser plugins found a user.  This is
+    # rare; some non-Auth::MailFrom authentication plugin which
+    # doesn't always return a current user?
+    MailError(
+        To          => $args{ErrorsTo},
+        Subject     => "Permission Denied",
+        Explanation => "You do not have permission to communicate with RT",
+        MIMEObj     => $args{Message},
+    );
+    FAILURE("Could not load a valid user");
+}
+
+=head2 CheckACL
 
     # Authentication Level
     # -1 - Get out.  this user has been explicitly declined
@@ -401,45 +443,38 @@ sub _LoadPlugins {
 
 =cut
 
-sub GetAuthenticationLevel {
+sub CheckACL {
     my %args = (
         MailPlugins   => [],
         Actions       => [],
         Message       => undef,
-        RawMessageRef => undef,
-        SystemTicket  => undef,
-        SystemQueue   => undef,
+        CurrentUser   => undef,
+        Ticket        => undef,
+        Queue         => undef,
         @_,
     );
 
-    my ( $CurrentUser, $AuthStat );
-
     # Initalize AuthStat so comparisons work correctly
-    $AuthStat = -9999999;
+    my $AuthStat = -9999999;
 
     # if plugin returns AuthStat -2 we skip action
     # NOTE: this is experimental API and it would be changed
     my %skip_action = ();
 
     # Since this needs loading, no matter what
-    foreach (@{ $args{MailPlugins} }) {
-        my ($Code, $NewAuthStat);
-        if ( ref($_) eq "CODE" ) {
-            $Code = $_;
-        } else {
-            $Code = $_->can("GetCurrentUser");
-            next unless $Code;
-        }
+    for my $class (@{ $args{MailPlugins} }) {
+        my $Code = $class->can("CheckACL");
+        next unless $Code;
 
         foreach my $action (@{ $args{Actions} }) {
-            ( $CurrentUser, $NewAuthStat ) = $Code->(
+            my $NewAuthStat = $Code->(
                 Message       => $args{Message},
                 RawMessageRef => $args{RawMessageRef},
-                CurrentUser   => $CurrentUser,
+                CurrentUser   => $args{CurrentUser},
                 AuthLevel     => $AuthStat,
                 Action        => $action,
-                Ticket        => $args{SystemTicket},
-                Queue         => $args{SystemQueue},
+                Ticket        => $args{Ticket},
+                Queue         => $args{Queue},
             );
 
 # You get the highest level of authentication you were assigned, unless you get the magic -1
@@ -458,7 +493,7 @@ sub GetAuthenticationLevel {
 
     return $AuthStat if !wantarray;
 
-    return ($AuthStat, $CurrentUser);
+    return $AuthStat;
 }
 
 =head3 _NoAuthorizedUserFound
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 05027d6..3ecf9e6 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -59,14 +59,10 @@ use RT::Interface::Email;
 # This is what the ordinary, non-enhanced gateway does at the moment.
 
 sub GetCurrentUser {
-    my %args = ( Message     => undef,
-                 CurrentUser => undef,
-                 AuthLevel   => undef,
-                 Ticket      => undef,
-                 Queue       => undef,
-                 Action      => undef,
-                 @_ );
-
+    my %args = (
+        Message => undef,
+        @_,
+    );
 
     # We don't need to do any external lookups
     my ( $Address, $Name, @errors ) = RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head );
@@ -83,7 +79,7 @@ sub GetCurrentUser {
     $CurrentUser->LoadByName( $Address ) unless $CurrentUser->Id;
     if ( $CurrentUser->Id ) {
         $RT::Logger->debug("Mail from user #". $CurrentUser->Id ." ($Address)" );
-        return ( $CurrentUser, 1 );
+        return $CurrentUser;
     }
 
 
@@ -97,7 +93,22 @@ sub GetCurrentUser {
     $CurrentUser = RT::CurrentUser->new;
     $CurrentUser->Load( $user->id );
 
-    return (undef, 0) unless $CurrentUser->id;
+    return $CurrentUser;
+}
+
+
+sub CheckACL {
+    my %args = (
+        Message     => undef,
+        CurrentUser => undef,
+        AuthLevel   => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        Action      => undef,
+        @_,
+    );
+
+    my $CurrentUser = $args{CurrentUser};
 
     if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
         my $qname = $args{'Queue'}->Name;
@@ -107,7 +118,7 @@ sub GetCurrentUser {
             # check to see whether if they can comment on the ticket
             unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to comment on ticket in queue '$qname'");
-                return ( $CurrentUser, 0 );
+                return 0;
             }
         }
         elsif ( $args{'Action'} =~ /^correspond$/i ) {
@@ -115,7 +126,7 @@ sub GetCurrentUser {
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to reply to ticket in queue '$qname'");
-                return ( $CurrentUser, 0 );
+                return 0;
             }
         }
         elsif ( $args{'Action'} =~ /^take$/i ) {
@@ -123,7 +134,7 @@ sub GetCurrentUser {
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to own ticket in queue '$qname'");
-                return ( $CurrentUser, 0 );
+                return 0;
             }
 
         }
@@ -132,13 +143,13 @@ sub GetCurrentUser {
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to resolve ticket in queue '$qname'");
-                return ( $CurrentUser, 0 );
+                return 0;
             }
 
         }
         else {
             $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
-            return ( $CurrentUser, 0 );
+            return 0;
         }
     }
 
@@ -149,11 +160,11 @@ sub GetCurrentUser {
         # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
         unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' ) ) {
             $RT::Logger->debug("Unprivileged users have no right to create ticket in queue '$qname'");
-            return ( $CurrentUser, 0 );
+            return 0;
         }
     }
 
-    return ( $CurrentUser, 1 );
+    return 1;
 }
 
 RT::Base->_ImportOverlays();
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index 89bfcee..4e1dba8 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -729,8 +729,8 @@ ok( $status, "successfuly granted right: $msg" );
 my $ace_id = $status;
 ok( $user->HasRight( Right => 'ReplyToTicket', Object => $tick ), "User can reply to ticket" );
 
-$m->next_warning_like(qr/That user may not own tickets in that queue/);
-$m->next_warning_like(qr/Could not record email: Ticket not taken/);
+$m->next_warning_like(qr/Permission Denied: You do not have permission to communicate with RT/);
+$m->next_warning_like(qr/Could not record email: ext-mailgate@\S+ tried to submit a message to ext-mailgate without permission/);
 $m->no_leftover_warnings_ok;
 
 $! = 0;

commit d6b673a6e606de4ff74e30a3823401940e2cd465
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 26 16:19:20 2014 -0500

    Remove no-longer-used _NoAuthorizedUserFound
    
    GetCurrentUser now either returns a current user, or fails.  The job of
    telling users about allowing CreateTicket will be re-implemented in the
    CheckACL code for ticket creation.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index e9d01cc..3e8a730 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -496,53 +496,6 @@ sub CheckACL {
     return $AuthStat;
 }
 
-=head3 _NoAuthorizedUserFound
-
-Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
-
-=cut
-
-sub _NoAuthorizedUserFound {
-    my %args = (
-        Right     => undef,
-        Message   => undef,
-        Requestor => undef,
-        Queue     => undef,
-        @_
-    );
-
-    # Notify the RT Admin of the failure.
-    MailError(
-        To          => RT->Config->Get('OwnerEmail'),
-        Subject     => "Could not load a valid user",
-        Explanation => <<EOT,
-RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for this email (@{[$args{Requestor}]}).
-
-You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
-queue @{[$args{'Queue'}]}.
-
-EOT
-        MIMEObj  => $args{'Message'},
-        LogLevel => 'error'
-    );
-
-    # Also notify the requestor that his request has been dropped.
-    if ($args{'Requestor'} ne RT->Config->Get('OwnerEmail')) {
-    MailError(
-        To          => $args{'Requestor'},
-        Subject     => "Could not load a valid user",
-        Explanation => <<EOT,
-RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for your email.
-
-EOT
-        MIMEObj  => $args{'Message'},
-        LogLevel => 'error'
-    );
-    }
-}
-
 =head3 ParseCcAddressesFromHead HASH
 
 Takes a hash containing QueueObj, Head and CurrentUser objects.

commit 7408373934527b2916df80b90e41f108a83847b2
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 12 01:59:54 2014 -0500

    Remove now-unused $Right variable, previously used by _NoAuthorizedUserFound

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 3e8a730..11a4e60 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -202,12 +202,6 @@ sub Gateway {
 
     my $SystemTicket = RT::Ticket->new( RT->SystemUser );
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
-    my $Right;
-    if ( $SystemTicket->id ) {
-        $Right = 'ReplyToTicket';
-    } else {
-        $Right = 'CreateTicket';
-    }
 
     # We can safely have no queue of we have a known-good ticket
     TMPFAIL("RT couldn't find the queue: " . $args{'queue'})

commit ddd1c0350b3e6acaa3c5d82c91837c5372aeac14
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 12 02:02:09 2014 -0500

    Fail if the first action is unauthenticated
    
    Instead of checking if any of the actions are allowed by any of the
    plugins, merely inspect the first action -- the process of completing it
    may alter the rights on the rest of the actions.  This also makes both
    authentication failure, and specifying the reason for the failure, much
    easier, as individual CheckACL plugins can simply call MailError and
    FAILURE with a custom message.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 11a4e60..e0c3b04 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -177,7 +177,6 @@ sub Gateway {
     $parser->_PostProcessNewEntity;
 
     my $head = $Message->head;
-    my $Sender = (ParseSenderAddressFromHead( $head ))[0];
     my $From = Encode::decode( "UTF-8", $head->get("From") );
     chomp $From if defined $From;
 
@@ -216,30 +215,18 @@ sub Gateway {
         Queue         => $SystemQueueObj,
     );
 
-    my $AuthStat = CheckACL(
+    # We only care about ACLs on the _first_ action, as later actions
+    # may have gotten rights by the time they happen.
+    CheckACL(
+        Action        => $actions[0],
+        ErrorsTo      => $ErrorsTo,
         MailPlugins   => \@mail_plugins,
-        Actions       => \@actions,
         Message       => $Message,
         CurrentUser   => $CurrentUser,
         Ticket        => $SystemTicket,
         Queue         => $SystemQueueObj,
     );
 
-    # If we got a user, but they don't have the right to say things
-    if ( $AuthStat == 0 ) {
-        MailError(
-            To          => $ErrorsTo,
-            Subject     => "Permission Denied",
-            Explanation =>
-                "You do not have permission to communicate with RT",
-            MIMEObj => $Message
-        );
-        FAILURE("$Sender tried to submit a message to "
-                . $args{'queue'}
-                . " without permission.",
-        );
-    }
-
     $head->replace('X-RT-Interface' => 'Email');
 
     # if plugin's updated SystemTicket then update arguments
@@ -439,8 +426,9 @@ sub GetCurrentUser {
 
 sub CheckACL {
     my %args = (
+        Action        => undef,
+        ErrorsTo      => undef,
         MailPlugins   => [],
-        Actions       => [],
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -448,46 +436,28 @@ sub CheckACL {
         @_,
     );
 
-    # Initalize AuthStat so comparisons work correctly
-    my $AuthStat = -9999999;
-
-    # if plugin returns AuthStat -2 we skip action
-    # NOTE: this is experimental API and it would be changed
-    my %skip_action = ();
-
-    # Since this needs loading, no matter what
     for my $class (@{ $args{MailPlugins} }) {
         my $Code = $class->can("CheckACL");
         next unless $Code;
 
-        foreach my $action (@{ $args{Actions} }) {
-            my $NewAuthStat = $Code->(
-                Message       => $args{Message},
-                RawMessageRef => $args{RawMessageRef},
-                CurrentUser   => $args{CurrentUser},
-                AuthLevel     => $AuthStat,
-                Action        => $action,
-                Ticket        => $args{Ticket},
-                Queue         => $args{Queue},
-            );
-
-# You get the highest level of authentication you were assigned, unless you get the magic -1
-# If a module returns a "-1" then we discard the ticket, so.
-            $AuthStat = $NewAuthStat
-                if ( $NewAuthStat > $AuthStat or $NewAuthStat == -2 );
-
-            $skip_action{$action}++ if $AuthStat == -2;
-        }
-
-        # strip actions we should skip
-        @{$args{Actions}} = grep !$skip_action{$_}, @{$args{Actions}}
-            if $AuthStat == -2;
-        last unless @{$args{Actions}};
+        return if $Code->(
+            ErrorsTo      => $args{ErrorsTo},
+            Message       => $args{Message},
+            CurrentUser   => $args{CurrentUser},
+            Action        => $args{Action},
+            Ticket        => $args{Ticket},
+            Queue         => $args{Queue},
+        );
     }
 
-    return $AuthStat if !wantarray;
-
-    return $AuthStat;
+    # Nobody said yes, and nobody said FAILURE; fail closed
+    MailError(
+        To          => $args{ErrorsTo},
+        Subject     => "Permission Denied",
+        Explanation => "You have no permission to $args{Action}",
+        MIMEObj     => $args{Message},
+    );
+    FAILURE( "You have no permission to $args{Action}" );
 }
 
 =head3 ParseCcAddressesFromHead HASH
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 3ecf9e6..84992eb 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -99,72 +99,74 @@ sub GetCurrentUser {
 
 sub CheckACL {
     my %args = (
+        ErrorsTo    => undef,
         Message     => undef,
         CurrentUser => undef,
-        AuthLevel   => undef,
         Ticket      => undef,
         Queue       => undef,
         Action      => undef,
         @_,
     );
 
-    my $CurrentUser = $args{CurrentUser};
+    my $principal = $args{CurrentUser}->PrincipalObj;
+    my $email     = $args{CurrentUser}->UserObj->EmailAddress;
 
+    my $msg;
     if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
         my $qname = $args{'Queue'}->Name;
+        my $tid   = $args{'Ticket'}->id;
         # We have a ticket. that means we're commenting or corresponding
         if ( $args{'Action'} =~ /^comment$/i ) {
 
             # check to see whether if they can comment on the ticket
-            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' ) ) {
-                $RT::Logger->debug("Unprivileged users have no right to comment on ticket in queue '$qname'");
-                return 0;
-            }
+            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' );
+            $msg = "$email has no right to comment on ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^correspond$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' ) ) {
-                $RT::Logger->debug("Unprivileged users have no right to reply to ticket in queue '$qname'");
-                return 0;
-            }
+            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' );
+            $msg = "$email has no right to reply to ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^take$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' ) ) {
-                $RT::Logger->debug("Unprivileged users have no right to own ticket in queue '$qname'");
-                return 0;
-            }
-
+            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' );
+            $msg = "$email has no right to own ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^resolve$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' ) ) {
-                $RT::Logger->debug("Unprivileged users have no right to resolve ticket in queue '$qname'");
-                return 0;
-            }
-
+            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' );
+            $msg = "$email has no right to resolve ticket $tid in queue $qname";
         }
         else {
             $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
-            return 0;
+            return;
         }
     }
 
     # We're creating a ticket
-    elsif ( $args{'Queue'} && $args{'Queue'}->Id ) {
+    elsif ( $args{'Action'} =~ /^(comment|correspond)$/i ) {
         my $qname = $args{'Queue'}->Name;
 
         # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
-        unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' ) ) {
-            $RT::Logger->debug("Unprivileged users have no right to create ticket in queue '$qname'");
-            return 0;
-        }
+        return 1 if $principal->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' );
+        $msg = "$email has no right to create tickets in queue $qname";
     }
+    else {
+        $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown with no ticket");
+        return;
+    }
+
 
-    return 1;
+    MailError(
+        To          => $args{ErrorsTo},
+        Subject     => "Permission Denied",
+        Explanation => $msg,
+        MIMEObj     => $args{Message},
+    );
+    FAILURE( $msg );
 }
 
 RT::Base->_ImportOverlays();
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index 4e1dba8..d87b3f8 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -194,8 +194,8 @@ EOF
     $u->Load("doesnotexist\@@{[RT->Config->Get('rtname')]}");
     ok( $u->Id, "user was created by failed ticket submission");
 
-    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist\@\S+ tried to submit a message to General without permission/);
+    $m->next_warning_like(qr/Permission Denied: doesnotexist\@\S+ has no right to create tickets in queue General/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist\@\S+ has no right to create tickets in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -253,8 +253,8 @@ EOF
     my $u = RT::User->new(RT->SystemUser);
     $u->Load('doesnotexist-2@'.RT->Config->Get('rtname'));
     ok( $u->Id, "user was created by ticket correspondence submission");
-    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist-2\@\S+ tried to submit a message to General without permission/);
+    $m->next_warning_like(qr/Permission Denied: doesnotexist-2\@\S+ has no right to reply to ticket $ticket_id in queue General/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist-2\@\S+ has no right to reply to ticket $ticket_id in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -337,8 +337,8 @@ EOF
     my $u = RT::User->new(RT->SystemUser);
     $u->Load('doesnotexist-3@'.RT->Config->Get('rtname'));
     ok( $u->Id, "user was created by ticket comment submission");
-    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist-3\@\S+ tried to submit a message to General without permission/);
+    $m->next_warning_like(qr/Permission Denied: doesnotexist-3\@\S+ has no right to comment on ticket $ticket_id in queue General/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist-3\@\S+ has no right to comment on ticket $ticket_id in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -729,8 +729,8 @@ ok( $status, "successfuly granted right: $msg" );
 my $ace_id = $status;
 ok( $user->HasRight( Right => 'ReplyToTicket', Object => $tick ), "User can reply to ticket" );
 
-$m->next_warning_like(qr/Permission Denied: You do not have permission to communicate with RT/);
-$m->next_warning_like(qr/Could not record email: ext-mailgate@\S+ tried to submit a message to ext-mailgate without permission/);
+$m->next_warning_like(qr/ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
+$m->next_warning_like(qr/Could not record email: ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
 $m->no_leftover_warnings_ok;
 
 $! = 0;
@@ -767,8 +767,8 @@ DBIx::SearchBuilder::Record::Cachable->FlushCache;
 cmp_ok( $tick->Owner, '!=', $user->id, "we didn't change owner" );
 is( $tick->Transactions->Count, 3, "no transactions added, user can't take ticket first" );
 
-$m->next_warning_like(qr/That user may not own tickets in that queue/);
-$m->next_warning_like(qr/Could not record email: Ticket not taken/);
+$m->next_warning_like(qr/ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
+$m->next_warning_like(qr/Could not record email: ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
 $m->no_leftover_warnings_ok;
 
 # revoke ReplyToTicket right

commit 03d0af9a7dfe03ce0f2f86f831bcfe19bb29f5a9
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Mar 11 14:21:16 2014 -0400

    Notify the owner on common mis-configurations
    
    This replicates the previous behavior covered by _NoAuthorizedUserFound

diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 84992eb..1116a49 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -127,6 +127,16 @@ sub CheckACL {
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' );
             $msg = "$email has no right to reply to ticket $tid in queue $qname";
+
+            # Also notify the owner
+            MailError(
+                To          => RT->Config->Get('OwnerEmail'),
+                Subject     => "Failed attempt to reply to a ticket by email, from $email",
+                Explanation => <<EOT,
+$email attempted to reply to a ticket via email in the queue $qname; you
+might need to grant 'Everyone' the ReplyToTicket right.
+EOT
+            );
         }
         elsif ( $args{'Action'} =~ /^take$/i ) {
 
@@ -153,6 +163,16 @@ sub CheckACL {
         # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
         return 1 if $principal->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' );
         $msg = "$email has no right to create tickets in queue $qname";
+
+        # Also notify the owner
+        MailError(
+            To          => RT->Config->Get('OwnerEmail'),
+            Subject     => "Failed attempt to create a ticket by email, from $email",
+            Explanation => <<EOT,
+$email attempted to create a ticket via email in the queue $qname; you
+might need to grant 'Everyone' the CreateTicket right.
+EOT
+        );
     }
     else {
         $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown with no ticket");
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index d87b3f8..684906b 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -194,6 +194,7 @@ EOF
     $u->Load("doesnotexist\@@{[RT->Config->Get('rtname')]}");
     ok( $u->Id, "user was created by failed ticket submission");
 
+    $m->next_warning_like(qr/Failed attempt to create a ticket by email, from doesnotexist\@\S+.*grant.*CreateTicket right/s);
     $m->next_warning_like(qr/Permission Denied: doesnotexist\@\S+ has no right to create tickets in queue General/);
     $m->next_warning_like(qr/Could not record email: doesnotexist\@\S+ has no right to create tickets in queue General/);
     $m->no_leftover_warnings_ok;
@@ -253,6 +254,7 @@ EOF
     my $u = RT::User->new(RT->SystemUser);
     $u->Load('doesnotexist-2@'.RT->Config->Get('rtname'));
     ok( $u->Id, "user was created by ticket correspondence submission");
+    $m->next_warning_like(qr/Failed attempt to reply to a ticket by email, from doesnotexist-2\@\S+.*grant.*ReplyToTicket right/s);
     $m->next_warning_like(qr/Permission Denied: doesnotexist-2\@\S+ has no right to reply to ticket $ticket_id in queue General/);
     $m->next_warning_like(qr/Could not record email: doesnotexist-2\@\S+ has no right to reply to ticket $ticket_id in queue General/);
     $m->no_leftover_warnings_ok;

commit 12115efd55ebffa959d93b34a21c9ed0309d3e90
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 12 03:19:33 2014 -0500

    Remove extra error in mail-gateway
    
    Let the mailgate control what error message are logged; often the error
    is merely repeating what was already logged.

diff --git a/share/html/REST/1.0/NoAuth/mail-gateway b/share/html/REST/1.0/NoAuth/mail-gateway
index 5b45a03..82f4afc 100644
--- a/share/html/REST/1.0/NoAuth/mail-gateway
+++ b/share/html/REST/1.0/NoAuth/mail-gateway
@@ -72,7 +72,6 @@ if ( $status == 1 ) {
   }
 }
 else {
-  $RT::Logger->error( "Could not record email: " . $error );
   if ( $status == -75 ) {
     $m->out( "temporary failure - $error\n" );
   }
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index 684906b..4e1c9c8 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -196,7 +196,6 @@ EOF
 
     $m->next_warning_like(qr/Failed attempt to create a ticket by email, from doesnotexist\@\S+.*grant.*CreateTicket right/s);
     $m->next_warning_like(qr/Permission Denied: doesnotexist\@\S+ has no right to create tickets in queue General/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist\@\S+ has no right to create tickets in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -256,7 +255,6 @@ EOF
     ok( $u->Id, "user was created by ticket correspondence submission");
     $m->next_warning_like(qr/Failed attempt to reply to a ticket by email, from doesnotexist-2\@\S+.*grant.*ReplyToTicket right/s);
     $m->next_warning_like(qr/Permission Denied: doesnotexist-2\@\S+ has no right to reply to ticket $ticket_id in queue General/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist-2\@\S+ has no right to reply to ticket $ticket_id in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -340,7 +338,6 @@ EOF
     $u->Load('doesnotexist-3@'.RT->Config->Get('rtname'));
     ok( $u->Id, "user was created by ticket comment submission");
     $m->next_warning_like(qr/Permission Denied: doesnotexist-3\@\S+ has no right to comment on ticket $ticket_id in queue General/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist-3\@\S+ has no right to comment on ticket $ticket_id in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -732,7 +729,6 @@ my $ace_id = $status;
 ok( $user->HasRight( Right => 'ReplyToTicket', Object => $tick ), "User can reply to ticket" );
 
 $m->next_warning_like(qr/ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
-$m->next_warning_like(qr/Could not record email: ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
 $m->no_leftover_warnings_ok;
 
 $! = 0;
@@ -751,7 +747,6 @@ cmp_ok( $tick->Owner, '!=', $user->id, "we didn't change owner" );
 is( $tick->Transactions->Count, 3, "one transactions added" );
 
 $m->next_warning_like(qr/That user may not own tickets in that queue/);
-$m->next_warning_like(qr/Could not record email: Ticket not taken/);
 $m->no_leftover_warnings_ok;
 
 $! = 0;
@@ -770,7 +765,6 @@ cmp_ok( $tick->Owner, '!=', $user->id, "we didn't change owner" );
 is( $tick->Transactions->Count, 3, "no transactions added, user can't take ticket first" );
 
 $m->next_warning_like(qr/ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
-$m->next_warning_like(qr/Could not record email: ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
 $m->no_leftover_warnings_ok;
 
 # revoke ReplyToTicket right

commit 096152b9879cd3fbc6bc954c355891673208598f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 26 16:52:05 2014 -0500

    Add back a warning that is now lacking
    
    Neither FAILURE nor EmailErrorToSender warn; this differs from the main
    codepaths in RT::Interface::Email in that MailError, unlike
    EmailErrorToSender, _does_ warn.

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 539d31f..3e07b1f 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -140,6 +140,7 @@ sub BeforeDecode {
                 Template  => 'Error: unencrypted message',
                 Arguments => { Message  => $args{'Message'} },
             );
+            $RT::Logger->warning("rejected because the message is unencrypted with RejectOnUnencrypted enabled");
             FAILURE('rejected because the message is unencrypted with RejectOnUnencrypted enabled');
         }
         else {

commit 3079bf8cb106f7aa1896f1583f2218d80cd0108d
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 26 19:50:10 2014 -0500

    Stop passing mail plugins around; load them lazily in one Plugins() method

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index e0c3b04..1ffe6da 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -62,6 +62,7 @@ use RT::Util 'safe_run_child';
 use File::Spec;
 use MIME::Words ();
 use Scope::Upper qw/unwind HERE/;
+use 5.010;
 
 =head1 NAME
 
@@ -152,18 +153,11 @@ sub Gateway {
         );
     }
 
-    my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
-    push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
-    @mail_plugins = _LoadPlugins( @mail_plugins );
-
     #Set up a queue object
     my $SystemQueueObj = RT::Queue->new( RT->SystemUser );
     $SystemQueueObj->Load( $args{'queue'} );
 
-    foreach my $class ( grep !ref, @mail_plugins ) {
-        # check if we should apply filter before decoding
-        my $Code = $class->can("BeforeDecode");
-        next unless $Code;
+    for my $Code ( Plugins(Method => "BeforeDecode") ) {
         $Code->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
@@ -208,7 +202,6 @@ sub Gateway {
 
     my $CurrentUser = GetCurrentUser(
         ErrorsTo      => $ErrorsTo,
-        MailPlugins   => \@mail_plugins,
         Message       => $Message,
         RawMessageRef => \$args{message},
         Ticket        => $SystemTicket,
@@ -220,7 +213,6 @@ sub Gateway {
     CheckACL(
         Action        => $actions[0],
         ErrorsTo      => $ErrorsTo,
-        MailPlugins   => \@mail_plugins,
         Message       => $Message,
         CurrentUser   => $CurrentUser,
         Ticket        => $SystemTicket,
@@ -342,30 +334,43 @@ sub IsCorrectAction {
     return ( 1, @actions );
 }
 
-sub _LoadPlugins {
-    my @mail_plugins = @_;
-
-    my @res;
-    foreach my $plugin (@mail_plugins) {
-        if ( ref($plugin) eq "CODE" ) {
-            push @res, $plugin;
-        } elsif ( !ref $plugin ) {
-            my $Class = $plugin;
-            $Class = "RT::Interface::Email::" . $Class
-                unless $Class =~ /^RT::/;
-            $Class->require or
-                do { $RT::Logger->error("Couldn't load $Class: $@"); next };
-
-            unless ( $Class->DOES( "RT::Interface::Email::Role" ) ) {
-                $RT::Logger->crit( "$Class does not implement RT::Interface::Email::Role.  Mail plugins from RT 4.2 and earlier are not forward-compatible with RT 4.4.");
-                next;
+sub Plugins {
+    my %args = (
+        Code => 0,
+        Method => undef,
+        @_
+    );
+    state @PLUGINS;
+
+    unless (@PLUGINS) {
+        my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
+        push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
+
+        foreach my $plugin (@mail_plugins) {
+            if ( ref($plugin) eq "CODE" ) {
+                push @PLUGINS, $plugin;
+            } elsif ( !ref $plugin ) {
+                my $Class = $plugin;
+                $Class = "RT::Interface::Email::" . $Class
+                    unless $Class =~ /^RT::/;
+                $Class->require or
+                    do { $RT::Logger->error("Couldn't load $Class: $@"); next };
+
+                unless ( $Class->DOES( "RT::Interface::Email::Role" ) ) {
+                    $RT::Logger->crit( "$Class does not implement RT::Interface::Email::Role.  Mail plugins from RT 4.2 and earlier are not forward-compatible with RT 4.4.");
+                    next;
+                }
+                push @PLUGINS, $Class;
+            } else {
+                $RT::Logger->crit( "$plugin - is not class name or code reference");
             }
-            push @res, $Class;
-        } else {
-            $RT::Logger->crit( "$plugin - is not class name or code reference");
         }
     }
-    return @res;
+
+    my @list = @PLUGINS;
+    @list = grep {not ref} @list unless $args{Code};
+    @list = grep {$_} map {ref $_ ? $_ : $_->can($args{Method})} @list if $args{Method};
+    return @list;
 }
 
 =head3 GetCurrentUser
@@ -375,7 +380,6 @@ sub _LoadPlugins {
 sub GetCurrentUser {
     my %args = (
         ErrorsTo      => undef,
-        MailPlugins   => [],
         Message       => undef,
         RawMessageRef => undef,
         Ticket        => undef,
@@ -384,15 +388,7 @@ sub GetCurrentUser {
     );
 
     # Since this needs loading, no matter what
-    foreach (@{ $args{MailPlugins} }) {
-        my $Code;
-        if ( ref($_) eq "CODE" ) {
-            $Code = $_;
-        } else {
-            $Code = $_->can("GetCurrentUser");
-            next unless $Code;
-        }
-
+    for my $Code ( Plugins(Code => 1, Method => "GetCurrentUser") ) {
         my $CurrentUser = $Code->(
             Message       => $args{Message},
             RawMessageRef => $args{RawMessageRef},
@@ -428,7 +424,6 @@ sub CheckACL {
     my %args = (
         Action        => undef,
         ErrorsTo      => undef,
-        MailPlugins   => [],
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -436,10 +431,7 @@ sub CheckACL {
         @_,
     );
 
-    for my $class (@{ $args{MailPlugins} }) {
-        my $Code = $class->can("CheckACL");
-        next unless $Code;
-
+    for my $Code ( Plugins( Method => "CheckACL" ) ) {
         return if $Code->(
             ErrorsTo      => $args{ErrorsTo},
             Message       => $args{Message},

commit 5ccf5a14fc05f958a82879d23e062564a8ee1c99
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 26 20:03:59 2014 -0500

    Split action handling into plugin classes
    
    This allows mail plugins to define their own actions that they handle,
    and dispatches handling an action to the first plugin that declares it
    can handle it.  Using this, the handling of correspond and comment, as
    well as the "unsafe" actions take and resolve, are moved out into their
    own plugins.
    
    As part of this, ParseCcAddressesFromHead, which was only called during
    ticket creation, is inlined and placed in _HandleCreate in the new
    RT::Interface::Email::Action::Defaults plugin, which is always enabled.
    
    The $UnsafeEmailCommands configuration is now accomplished by adding
    Action::Take and/or Action::Resolve to @MailPlugins.

diff --git a/bin/rt-mailgate.in b/bin/rt-mailgate.in
index 9b0b8d4..8498243 100644
--- a/bin/rt-mailgate.in
+++ b/bin/rt-mailgate.in
@@ -329,15 +329,15 @@ Usual invocation (from MTA):
 
 =item C<--action>
 
-Specifies what happens to email sent to this alias.  The avaliable
-basic actions are: C<correspond>, C<comment>.
-
-
-If you've set the RT configuration variable B<< C<UnsafeEmailCommands> >>,
-C<take> and C<resolve> are also available.  You can execute two or more
-actions on a single message using a C<-> separated list.  RT will execute
-the actions in the listed order.  For example you can use C<take-comment>,
-C<correspond-resolve> or C<take-comment-resolve> as actions.
+Specifies what happens to email sent to this alias.  The avaliable basic
+actions are: C<correspond>, C<comment>.  Additional actions, such as
+C<take> or C<resolve>, may be available depending on your local
+C<@MailPlugins> configuration.
+
+You can execute two or more actions on a single message using a C<->
+separated list.  RT will execute the actions in the listed order.  For
+example you can use C<take-comment>, C<correspond-resolve> or
+C<take-comment-resolve> as actions.
 
 Note that C<take> and C<resolve> actions ignore message text if used
 alone.  Include a  C<comment> or C<correspond> action if you want RT
diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
index 7570c28..fe53e64 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -42,6 +42,12 @@ The L<RT_Config/@MailPlugins> functionality has been rewritten; mail
 plugins written for previous versions of RT will not function as
 expected.  See F<docs/extending/mail_plugins.pod>
 
+=item *
+
+The C<$UnsafeEmailCommands> option has been replaced with two mail
+plugins, L<RT::Interface::Email::Action::Take>, and
+L<RT::Interface::Email::Action::Resolve>.
+
 =back
 
 =cut
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 92ee872..08e5e09 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -456,15 +456,6 @@ L<RT::Interface::Email> to use; see L<rt-mailgate>
 
 =cut
 
-=item C<$UnsafeEmailCommands>
-
-C<$UnsafeEmailCommands>, if set to 1, enables 'take' and 'resolve'
-as possible actions via the mail gateway.  As its name implies, this
-is very unsafe, as it allows email with a forged sender to possibly
-resolve arbitrary tickets!
-
-=cut
-
 =item C<$ExtractSubjectTagMatch>, C<$ExtractSubjectTagNoMatch>
 
 The default "extract remote tracking tags" scrip settings; these
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 1ffe6da..135b197 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -124,14 +124,12 @@ sub Gateway {
     # Set the scope to return from with TMPFAIL/FAILURE/SUCCESS
     $SCOPE = HERE;
 
-    # Validate the action
-    my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
-    TMPFAIL(
-        "Invalid 'action' parameter "
-            . $actions[0]
-            . " for queue "
-            . $args{'queue'},
-    ) unless $status;
+    # Validate the actions
+    my @actions = grep $_, split /-/, $args{action};
+    for my $action (@actions) {
+        TMPFAIL( "Invalid 'action' parameter $action for queue $args{queue}" )
+            unless Plugins(Method => "Handle" . ucfirst($action));
+    }
 
     my $parser = RT::EmailParser->new();
     $parser->SmartParseMIMEEntityFromScalar(
@@ -221,119 +219,23 @@ sub Gateway {
 
     $head->replace('X-RT-Interface' => 'Email');
 
-    # if plugin's updated SystemTicket then update arguments
-    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
-
     my $Ticket = RT::Ticket->new($CurrentUser);
-
-    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
-    {
-
-        my @Cc;
-        my @Requestors = ( $CurrentUser->id );
-
-        if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
-            @Cc = ParseCcAddressesFromHead(
-                Head        => $head,
-                CurrentUser => $CurrentUser,
-                QueueObj    => $SystemQueueObj
-            );
-        }
-
-        # ExtractTicketId may have been overridden, and edited the Subject
-        my $NewSubject = Encode::decode( "UTF-8", $head->get('Subject') );
-        chomp $NewSubject;
-
-        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
-            Queue     => $SystemQueueObj->Id,
-            Subject   => $NewSubject,
-            Requestor => \@Requestors,
-            Cc        => \@Cc,
-            MIMEObj   => $Message
+    $Ticket->Load( $SystemTicket->Id );
+
+    for my $action (@actions) {
+        HandleAction(
+            Action      => $action,
+            ErrorsTo    => $ErrorsTo,
+            Subject     => $Subject,
+            Message     => $Message,
+            Ticket      => $Ticket,
+            TicketId    => $args{ticket},
+            Queue       => $SystemQueueObj,
         );
-        if ( $id == 0 ) {
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "Ticket creation failed: $Subject",
-                Explanation => $ErrStr,
-                MIMEObj     => $Message
-            );
-            FAILURE("Ticket creation From: $From failed: $ErrStr", $Ticket );
-        }
-
-        # strip comments&corresponds from the actions we don't need
-        # to record them if we've created the ticket just now
-        @actions = grep !/^(comment|correspond)$/, @actions;
-        $args{'ticket'} = $id;
-
-    } 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",
-                Explanation => $error,
-                MIMEObj     => $Message
-            );
-
-            FAILURE( $error );
-        }
-        $args{'ticket'} = $Ticket->id;
-    } else {
-        SUCCESS( $Ticket );
-    }
-
-    # }}}
-
-    my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
-    foreach my $action (@actions) {
-
-        #   If the action is comment, add a comment.
-        if ( $action =~ /^(?:comment|correspond)$/i ) {
-            my $method = ucfirst lc $action;
-            my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );
-            unless ($status) {
-
-                #Warn the sender that we couldn't actually submit the comment.
-                MailError(
-                    To          => $ErrorsTo,
-                    Subject     => "Message not recorded ($method): $Subject",
-                    Explanation => $msg,
-                    MIMEObj     => $Message
-                );
-                FAILURE( "Message From: $From not recorded: $msg", $Ticket );
-            }
-        } elsif ($unsafe_actions) {
-            _RunUnsafeAction(
-                Action      => $action,
-                ErrorsTo    => $ErrorsTo,
-                Message     => $Message,
-                Ticket      => $Ticket,
-                CurrentUser => $CurrentUser,
-            );
-        }
     }
     SUCCESS( $Ticket );
 }
 
-=head3 IsCorrectAction
-
-Returns a list of valid actions we've found for this message
-
-=cut
-
-sub IsCorrectAction {
-    my $action = shift;
-    my @actions = grep $_, split /-/, $action;
-    return ( 0, '(no value)' ) unless @actions;
-    foreach ( @actions ) {
-        return ( 0, $_ ) unless /^(?:comment|correspond|take|resolve)$/;
-    }
-    return ( 1, @actions );
-}
-
 sub Plugins {
     my %args = (
         Code => 0,
@@ -345,6 +247,7 @@ sub Plugins {
     unless (@PLUGINS) {
         my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
         push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
+        push @mail_plugins, "Action::Defaults";
 
         foreach my $plugin (@mail_plugins) {
             if ( ref($plugin) eq "CODE" ) {
@@ -452,36 +355,23 @@ sub CheckACL {
     FAILURE( "You have no permission to $args{Action}" );
 }
 
-=head3 ParseCcAddressesFromHead HASH
-
-Takes a hash containing QueueObj, Head and CurrentUser objects.
-Returns a list of all email addresses in the To and Cc
-headers b<except> the current Queue's email addresses, the CurrentUser's
-email address  and anything that the configuration sub RT::IsRTAddress matches.
-
-=cut
-
-sub ParseCcAddressesFromHead {
+sub HandleAction {
     my %args = (
-        Head        => undef,
-        QueueObj    => undef,
-        CurrentUser => undef,
+        Action   => undef,
+        ErrorsTo => undef,
+        Subject  => undef,
+        Message  => undef,
+        Ticket   => undef,
+        TicketId => undef,
+        Queue    => undef,
         @_
     );
 
-    my $current_address = lc $args{'CurrentUser'}->EmailAddress;
-    my $user = $args{'CurrentUser'}->UserObj;
-
-    return
-        grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ),
-        map lc $user->CanonicalizeEmailAddress( $_->address ),
-        map RT::EmailParser->CleanupAddresses( Email::Address->parse(
-              Encode::decode( "UTF-8", $args{'Head'}->get( $_ ) ) ) ),
-        qw(To Cc);
+    my ($code) = Plugins(Method => "Handle" . ucfirst(delete $args{Action}));
+    $code->(%args);
 }
 
 
-
 =head3 ParseSenderAddressFromHead HEAD
 
 Takes a MIME::Header object. Returns (user at host, friendly name, errors)
@@ -782,50 +672,6 @@ sub ParseTicketId {
     return $id;
 }
 
-sub _RunUnsafeAction {
-    my %args = (
-        Action      => undef,
-        ErrorsTo    => undef,
-        Message     => undef,
-        Ticket      => undef,
-        CurrentUser => undef,
-        @_
-    );
-
-    my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
-
-    if ( $args{'Action'} =~ /^take$/i ) {
-        my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{'CurrentUser'}->id );
-        unless ($status) {
-            MailError(
-                To          => $args{'ErrorsTo'},
-                Subject     => "Ticket not taken",
-                Explanation => $msg,
-                MIMEObj     => $args{'Message'}
-            );
-            FAILURE( "Ticket not taken, by email From: $From" );
-        }
-    } elsif ( $args{'Action'} =~ /^resolve$/i ) {
-        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'}
-                );
-                FAILURE( "Ticket not resolved, by email From: $From" );
-            }
-        }
-    } else {
-        FAILURE( "Not supported unsafe action $args{'Action'}, by email From: $From", $args{'Ticket'} );
-    }
-}
-
 =head3 MailError PARAM HASH
 
 Sends an error message. Takes a param hash:
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
new file mode 100644
index 0000000..239c47e
--- /dev/null
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -0,0 +1,146 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at 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., 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:
+#
+# (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
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# 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::Action::Defaults;
+
+use strict;
+use warnings;
+
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
+use RT::Interface::Email;
+
+sub _HandleCreate {
+    my %args = (
+        ErrorsTo    => undef,
+        Subject     => undef,
+        Message     => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    my $head = $args{Message}->head;
+
+    my @Cc;
+    my @Requestors = ( $args{Ticket}->CurrentUser->id );
+    if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
+        my $user = $args{Ticket}->CurrentUser->UserObj;
+        my $current_address = lc $user->EmailAddress;
+
+        @Cc =
+            grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ),
+            map lc $user->CanonicalizeEmailAddress( $_->address ),
+            map RT::EmailParser->CleanupAddresses( Email::Address->parse(
+                  Encode::decode( "UTF-8", $head->get( $_ ) ) ) ),
+            qw(To Cc);
+    }
+
+    # ExtractTicketId may have been overridden, and edited the Subject
+    my $subject = Encode::decode( "UTF-8", $head->get('Subject') );
+    chomp $subject;
+
+    my ( $id, $Transaction, $ErrStr ) = $args{Ticket}->Create(
+        Queue     => $args{Queue}->Id,
+        Subject   => $subject,
+        Requestor => \@Requestors,
+        Cc        => \@Cc,
+        MIMEObj   => $args{Message},
+    );
+    return if $id;
+
+    MailError(
+        To          => $args{ErrorsTo},
+        Subject     => "Ticket creation failed: $args{Subject}",
+        Explanation => $ErrStr,
+        MIMEObj     => $args{Message},
+    );
+    FAILURE("Ticket creation failed: $ErrStr", $args{Ticket} );
+}
+
+sub HandleComment {
+    _HandleEither( @_, Action => "Comment" );
+}
+
+sub HandleCorrespond {
+    _HandleEither( @_, Action => "Correspond" );
+}
+
+sub _HandleEither {
+    my %args = (
+        Action      => undef,
+        ErrorsTo    => undef,
+        Message     => undef,
+        Subject     => undef,
+        Ticket      => undef,
+        TicketId    => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    return _HandleCreate(@_) unless $args{TicketId};
+
+    unless ( $args{Ticket}->Id ) {
+        my $error = "Could not find a ticket with id " . $args{TicketId};
+        MailError(
+            To          => $args{ErrorsTo},
+            Subject     => "Message not recorded: $args{Subject}",
+            Explanation => $error,
+            MIMEObj     => $args{Message}
+        );
+        FAILURE( $error );
+    }
+
+    my $action = ucfirst $args{Action};
+    my ( $status, $msg ) = $args{Ticket}->$action( MIMEObj => $args{Message} );
+    return if $status;
+}
+
+1;
+
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
new file mode 100644
index 0000000..0b4a688
--- /dev/null
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -0,0 +1,96 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at 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., 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:
+#
+# (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
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# 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::Action::Resolve;
+
+use strict;
+use warnings;
+
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
+sub HandleResolve {
+    my %args = (
+        ErrorsTo    => undef,
+        Message     => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    unless ( $args{Ticket}->Id ) {
+        my $error = "Could not find a ticket with id " . $args{TicketId};
+        MailError(
+            To          => $args{ErrorsTo},
+            Subject     => "Message not recorded: $args{Subject}",
+            Explanation => $error,
+            MIMEObj     => $args{Message}
+        );
+        FAILURE( $error );
+    }
+
+    my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
+
+    my $new_status = $args{'Ticket'}->FirstInactiveStatus;
+    return unless $new_status;
+
+    my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status);
+    return if $status;
+
+    # Warn the sender that we couldn't actually resolve the ticket
+    MailError(
+        To          => $args{'ErrorsTo'},
+        Subject     => "Ticket not resolved",
+        Explanation => $msg,
+        MIMEObj     => $args{'Message'}
+    );
+    FAILURE( "Ticket not resolved, by email From: $From" );
+}
+
+1;
+
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
new file mode 100644
index 0000000..8546828
--- /dev/null
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -0,0 +1,92 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at 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., 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:
+#
+# (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
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# 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::Action::Take;
+
+use strict;
+use warnings;
+
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
+sub HandleTake {
+    my %args = (
+        ErrorsTo    => undef,
+        Message     => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    unless ( $args{Ticket}->Id ) {
+        my $error = "Could not find a ticket with id " . $args{TicketId};
+        MailError(
+            To          => $args{ErrorsTo},
+            Subject     => "Message not recorded: $args{Subject}",
+            Explanation => $error,
+            MIMEObj     => $args{Message}
+        );
+        FAILURE( $error );
+    }
+
+    my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
+
+    my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{Ticket}->CurrentUser->id );
+    return if $status;
+
+    MailError(
+        To          => $args{'ErrorsTo'},
+        Subject     => "Ticket not taken",
+        Explanation => $msg,
+        MIMEObj     => $args{'Message'}
+    );
+    FAILURE( "Ticket not taken, by email From: $From" );
+}
+
+1;
+
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 40911f8..e2e8c85 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -1244,7 +1244,7 @@ sub FirstActiveStatus {
 Returns the first inactive status that the ticket could transition to,
 according to its current Queue's lifecycle.  May return undef if there
 is no such possible status to transition to, or we are already in it.
-This is used in resolve action in UnsafeEmailCommands, for instance.
+This is used in L<RT::Interface::Email::Action::Resolve>, for instance.
 
 =cut
 
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index 4e1c9c8..e955233 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 
-use RT::Test config => 'Set( $UnsafeEmailCommands, 1);', tests => undef, actual_server => 1;
+use RT::Test config => 'Set( @MailPlugins, "Auth::MailFrom", "Action::Take", "Action::Resolve");', tests => undef, actual_server => 1;
 my ($baseurl, $m) = RT::Test->started_ok;
 
 use RT::Tickets;
@@ -600,10 +600,6 @@ EOF
 my ($val,$msg) = $everyone_group->PrincipalObj->RevokeRight(Right => 'CreateTicket');
 ok ($val, $msg);
 
-SKIP: {
-skip "Advanced mailgate actions require an unsafe configuration", 47
-    unless RT->Config->Get('UnsafeEmailCommands');
-
 # create new queue to be shure we don't mess with rights
 use RT::Queue;
 my $queue = RT::Queue->new(RT->SystemUser);
@@ -813,7 +809,5 @@ is( $tick->Transactions->Count, 6, "transactions added" );
 
 $m->no_warnings_ok;
 
-};
-
 undef $m;
 done_testing;

commit 2f24bb090933b084e635f01be480608ea6108a06
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun Mar 9 12:12:23 2014 -0400

    Plugins may alter @actions; ensure action is valid prior to calling

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 135b197..9124db3 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -367,7 +367,11 @@ sub HandleAction {
         @_
     );
 
-    my ($code) = Plugins(Method => "Handle" . ucfirst(delete $args{Action}));
+    my $action = delete $args{Action};
+    my ($code) = Plugins(Method => "Handle" . ucfirst($action));
+    TMPFAIL( "Invalid 'action' parameter $action for queue ".$args{Queue}->Name )
+        unless $code;
+
     $code->(%args);
 }
 

commit a6838f4dd0a175c0418e130ee235dbe1720555df
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 26 20:04:47 2014 -0500

    Split default authentication from default authorization

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 9124db3..2b40535 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -247,6 +247,7 @@ sub Plugins {
     unless (@PLUGINS) {
         my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
         push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
+        push @mail_plugins, "Authz::Default";
         push @mail_plugins, "Action::Defaults";
 
         foreach my $plugin (@mail_plugins) {
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 1116a49..f92bfe1 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -54,10 +54,6 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
-use RT::Interface::Email;
-
-# This is what the ordinary, non-enhanced gateway does at the moment.
-
 sub GetCurrentUser {
     my %args = (
         Message => undef,
@@ -96,99 +92,4 @@ sub GetCurrentUser {
     return $CurrentUser;
 }
 
-
-sub CheckACL {
-    my %args = (
-        ErrorsTo    => undef,
-        Message     => undef,
-        CurrentUser => undef,
-        Ticket      => undef,
-        Queue       => undef,
-        Action      => undef,
-        @_,
-    );
-
-    my $principal = $args{CurrentUser}->PrincipalObj;
-    my $email     = $args{CurrentUser}->UserObj->EmailAddress;
-
-    my $msg;
-    if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
-        my $qname = $args{'Queue'}->Name;
-        my $tid   = $args{'Ticket'}->id;
-        # We have a ticket. that means we're commenting or corresponding
-        if ( $args{'Action'} =~ /^comment$/i ) {
-
-            # check to see whether if they can comment on the ticket
-            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' );
-            $msg = "$email has no right to comment on ticket $tid in queue $qname";
-        }
-        elsif ( $args{'Action'} =~ /^correspond$/i ) {
-
-            # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' );
-            $msg = "$email has no right to reply to ticket $tid in queue $qname";
-
-            # Also notify the owner
-            MailError(
-                To          => RT->Config->Get('OwnerEmail'),
-                Subject     => "Failed attempt to reply to a ticket by email, from $email",
-                Explanation => <<EOT,
-$email attempted to reply to a ticket via email in the queue $qname; you
-might need to grant 'Everyone' the ReplyToTicket right.
-EOT
-            );
-        }
-        elsif ( $args{'Action'} =~ /^take$/i ) {
-
-            # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' );
-            $msg = "$email has no right to own ticket $tid in queue $qname";
-        }
-        elsif ( $args{'Action'} =~ /^resolve$/i ) {
-
-            # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' );
-            $msg = "$email has no right to resolve ticket $tid in queue $qname";
-        }
-        else {
-            $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
-            return;
-        }
-    }
-
-    # We're creating a ticket
-    elsif ( $args{'Action'} =~ /^(comment|correspond)$/i ) {
-        my $qname = $args{'Queue'}->Name;
-
-        # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
-        return 1 if $principal->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' );
-        $msg = "$email has no right to create tickets in queue $qname";
-
-        # Also notify the owner
-        MailError(
-            To          => RT->Config->Get('OwnerEmail'),
-            Subject     => "Failed attempt to create a ticket by email, from $email",
-            Explanation => <<EOT,
-$email attempted to create a ticket via email in the queue $qname; you
-might need to grant 'Everyone' the CreateTicket right.
-EOT
-        );
-    }
-    else {
-        $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown with no ticket");
-        return;
-    }
-
-
-    MailError(
-        To          => $args{ErrorsTo},
-        Subject     => "Permission Denied",
-        Explanation => $msg,
-        MIMEObj     => $args{Message},
-    );
-    FAILURE( $msg );
-}
-
-RT::Base->_ImportOverlays();
-
 1;
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Authz/Default.pm
similarity index 71%
copy from lib/RT/Interface/Email/Auth/MailFrom.pm
copy to lib/RT/Interface/Email/Authz/Default.pm
index 1116a49..cdc11f0 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -2,7 +2,7 @@
 #
 # COPYRIGHT:
 #
-# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
 #                                          <sales at bestpractical.com>
 #
 # (Except where explicitly superseded by other copyright notices)
@@ -46,7 +46,7 @@
 #
 # END BPS TAGGED BLOCK }}}
 
-package RT::Interface::Email::Auth::MailFrom;
+package RT::Interface::Email::Authz::Default;
 
 use strict;
 use warnings;
@@ -54,49 +54,6 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
-use RT::Interface::Email;
-
-# This is what the ordinary, non-enhanced gateway does at the moment.
-
-sub GetCurrentUser {
-    my %args = (
-        Message => undef,
-        @_,
-    );
-
-    # We don't need to do any external lookups
-    my ( $Address, $Name, @errors ) = RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head );
-    $RT::Logger->warning("Failed to parse ".join(', ', @errors))
-        if @errors;
-
-    unless ( $Address ) {
-        $RT::Logger->error("Couldn't parse or find sender's address");
-        FAILURE("Couldn't parse or find sender's address");
-    }
-
-    my $CurrentUser = RT::CurrentUser->new;
-    $CurrentUser->LoadByEmail( $Address );
-    $CurrentUser->LoadByName( $Address ) unless $CurrentUser->Id;
-    if ( $CurrentUser->Id ) {
-        $RT::Logger->debug("Mail from user #". $CurrentUser->Id ." ($Address)" );
-        return $CurrentUser;
-    }
-
-
-    my $user = RT::User->new( RT->SystemUser );
-    $user->LoadOrCreateByEmail(
-        RealName     => $Name,
-        EmailAddress => $Address,
-        Comments     => 'Autocreated on ticket submission',
-    );
-
-    $CurrentUser = RT::CurrentUser->new;
-    $CurrentUser->Load( $user->id );
-
-    return $CurrentUser;
-}
-
-
 sub CheckACL {
     my %args = (
         ErrorsTo    => undef,
@@ -110,21 +67,17 @@ sub CheckACL {
 
     my $principal = $args{CurrentUser}->PrincipalObj;
     my $email     = $args{CurrentUser}->UserObj->EmailAddress;
+    my $qname     = $args{'Queue'}->Name;
 
     my $msg;
     if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
-        my $qname = $args{'Queue'}->Name;
         my $tid   = $args{'Ticket'}->id;
-        # We have a ticket. that means we're commenting or corresponding
-        if ( $args{'Action'} =~ /^comment$/i ) {
 
-            # check to see whether if they can comment on the ticket
+        if ( $args{'Action'} =~ /^comment$/i ) {
             return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' );
             $msg = "$email has no right to comment on ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^correspond$/i ) {
-
-            # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' );
             $msg = "$email has no right to reply to ticket $tid in queue $qname";
 
@@ -139,14 +92,10 @@ EOT
             );
         }
         elsif ( $args{'Action'} =~ /^take$/i ) {
-
-            # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' );
             $msg = "$email has no right to own ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^resolve$/i ) {
-
-            # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' );
             $msg = "$email has no right to resolve ticket $tid in queue $qname";
         }
@@ -158,9 +107,6 @@ EOT
 
     # We're creating a ticket
     elsif ( $args{'Action'} =~ /^(comment|correspond)$/i ) {
-        my $qname = $args{'Queue'}->Name;
-
-        # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
         return 1 if $principal->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' );
         $msg = "$email has no right to create tickets in queue $qname";
 
@@ -189,6 +135,4 @@ EOT
     FAILURE( $msg );
 }
 
-RT::Base->_ImportOverlays();
-
 1;

commit 46516528a8ca8b0a37f4dba0359b2b94ec94f162
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 26 20:23:48 2014 -0500

    Local'ize MailErrors to avoid having to pass $ErrorsTo everywhere

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 2b40535..901301e 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -110,7 +110,6 @@ sub TMPFAIL { unwind (-75,     $_[0], undef, => $SCOPE) }
 sub FAILURE { unwind (  0,     $_[0], $_[1], => $SCOPE) }
 sub SUCCESS { unwind (  1, "Success", $_[0], => $SCOPE) }
 
-
 sub Gateway {
     my $argsref = shift;
     my %args    = (
@@ -184,11 +183,17 @@ sub Gateway {
     $ErrorsTo = RT->Config->Get('OwnerEmail')
         if IsMachineGeneratedMail(
             Message   => $Message,
-            ErrorsTo  => $ErrorsTo,
             Subject   => $Subject,
             MessageId => $MessageId,
         );
 
+    # Make all errors from here on out bounce back to $ErrorsTo
+    my $bare_MailError = \&MailError;
+    no warnings 'redefine';
+    local *MailError = sub {
+        $bare_MailError->(To => $ErrorsTo, MIMEObj => $Message, @_)
+    };
+
     $args{'ticket'} ||= ExtractTicketId( $Message );
 
     my $SystemTicket = RT::Ticket->new( RT->SystemUser );
@@ -199,7 +204,6 @@ sub Gateway {
         unless $SystemTicket->id || $SystemQueueObj->id;
 
     my $CurrentUser = GetCurrentUser(
-        ErrorsTo      => $ErrorsTo,
         Message       => $Message,
         RawMessageRef => \$args{message},
         Ticket        => $SystemTicket,
@@ -210,7 +214,6 @@ sub Gateway {
     # may have gotten rights by the time they happen.
     CheckACL(
         Action        => $actions[0],
-        ErrorsTo      => $ErrorsTo,
         Message       => $Message,
         CurrentUser   => $CurrentUser,
         Ticket        => $SystemTicket,
@@ -225,7 +228,6 @@ sub Gateway {
     for my $action (@actions) {
         HandleAction(
             Action      => $action,
-            ErrorsTo    => $ErrorsTo,
             Subject     => $Subject,
             Message     => $Message,
             Ticket      => $Ticket,
@@ -283,7 +285,6 @@ sub Plugins {
 
 sub GetCurrentUser {
     my %args = (
-        ErrorsTo      => undef,
         Message       => undef,
         RawMessageRef => undef,
         Ticket        => undef,
@@ -306,10 +307,8 @@ sub GetCurrentUser {
     # rare; some non-Auth::MailFrom authentication plugin which
     # doesn't always return a current user?
     MailError(
-        To          => $args{ErrorsTo},
         Subject     => "Permission Denied",
         Explanation => "You do not have permission to communicate with RT",
-        MIMEObj     => $args{Message},
     );
     FAILURE("Could not load a valid user");
 }
@@ -327,7 +326,6 @@ sub GetCurrentUser {
 sub CheckACL {
     my %args = (
         Action        => undef,
-        ErrorsTo      => undef,
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -337,7 +335,6 @@ sub CheckACL {
 
     for my $Code ( Plugins( Method => "CheckACL" ) ) {
         return if $Code->(
-            ErrorsTo      => $args{ErrorsTo},
             Message       => $args{Message},
             CurrentUser   => $args{CurrentUser},
             Action        => $args{Action},
@@ -348,10 +345,8 @@ sub CheckACL {
 
     # Nobody said yes, and nobody said FAILURE; fail closed
     MailError(
-        To          => $args{ErrorsTo},
         Subject     => "Permission Denied",
         Explanation => "You have no permission to $args{Action}",
-        MIMEObj     => $args{Message},
     );
     FAILURE( "You have no permission to $args{Action}" );
 }
@@ -359,7 +354,6 @@ sub CheckACL {
 sub HandleAction {
     my %args = (
         Action   => undef,
-        ErrorsTo => undef,
         Subject  => undef,
         Message  => undef,
         Ticket   => undef,
@@ -504,7 +498,6 @@ sub IsMachineGeneratedMail {
                 To          => $owner_mail,
                 Subject     => "RT Bounce: ".$args{'Subject'},
                 Explanation => "RT thinks this message may be a bounce",
-                MIMEObj     => $args{Message}
             );
         }
 
@@ -687,8 +680,6 @@ Sends an error message. Takes a param hash:
 
 =item To - recipient, by default is 'OwnerEmail';
 
-=item Bcc - optional Bcc recipients;
-
 =item Subject - subject of the message, default is 'There has been an error';
 
 =item Explanation - main content of the error, default value is 'Unexplained error';
@@ -708,7 +699,6 @@ explanation message into the log, by default we log it as critical.
 sub MailError {
     my %args = (
         To          => RT->Config->Get('OwnerEmail'),
-        Bcc         => undef,
         From        => RT->Config->Get('CorrespondAddress'),
         Subject     => 'There has been an error',
         Explanation => 'Unexplained error',
@@ -727,7 +717,6 @@ sub MailError {
     my %entity_args = (
         Type                    => "multipart/mixed",
         From                    => Encode::encode( "UTF-8", $args{'From'} ),
-        Bcc                     => Encode::encode( "UTF-8", $args{'Bcc'} ),
         To                      => Encode::encode( "UTF-8", $args{'To'} ),
         Subject                 => EncodeToMIME( String => $args{'Subject'} ),
         'X-RT-Loop-Prevention:' => Encode::encode( "UTF-8", RT->Config->Get('rtname') ),
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
index 239c47e..105b356 100644
--- a/lib/RT/Interface/Email/Action/Defaults.pm
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -58,7 +58,6 @@ use RT::Interface::Email;
 
 sub _HandleCreate {
     my %args = (
-        ErrorsTo    => undef,
         Subject     => undef,
         Message     => undef,
         Ticket      => undef,
@@ -96,10 +95,8 @@ sub _HandleCreate {
     return if $id;
 
     MailError(
-        To          => $args{ErrorsTo},
         Subject     => "Ticket creation failed: $args{Subject}",
         Explanation => $ErrStr,
-        MIMEObj     => $args{Message},
     );
     FAILURE("Ticket creation failed: $ErrStr", $args{Ticket} );
 }
@@ -115,7 +112,6 @@ sub HandleCorrespond {
 sub _HandleEither {
     my %args = (
         Action      => undef,
-        ErrorsTo    => undef,
         Message     => undef,
         Subject     => undef,
         Ticket      => undef,
@@ -129,10 +125,8 @@ sub _HandleEither {
     unless ( $args{Ticket}->Id ) {
         my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
-            To          => $args{ErrorsTo},
             Subject     => "Message not recorded: $args{Subject}",
             Explanation => $error,
-            MIMEObj     => $args{Message}
         );
         FAILURE( $error );
     }
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
index 0b4a688..daaa3dd 100644
--- a/lib/RT/Interface/Email/Action/Resolve.pm
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -56,7 +56,6 @@ with 'RT::Interface::Email::Role';
 
 sub HandleResolve {
     my %args = (
-        ErrorsTo    => undef,
         Message     => undef,
         Ticket      => undef,
         Queue       => undef,
@@ -66,10 +65,8 @@ sub HandleResolve {
     unless ( $args{Ticket}->Id ) {
         my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
-            To          => $args{ErrorsTo},
             Subject     => "Message not recorded: $args{Subject}",
             Explanation => $error,
-            MIMEObj     => $args{Message}
         );
         FAILURE( $error );
     }
@@ -84,10 +81,8 @@ sub HandleResolve {
 
     # Warn the sender that we couldn't actually resolve the ticket
     MailError(
-        To          => $args{'ErrorsTo'},
         Subject     => "Ticket not resolved",
         Explanation => $msg,
-        MIMEObj     => $args{'Message'}
     );
     FAILURE( "Ticket not resolved, by email From: $From" );
 }
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
index 8546828..60b9b15 100644
--- a/lib/RT/Interface/Email/Action/Take.pm
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -56,7 +56,6 @@ with 'RT::Interface::Email::Role';
 
 sub HandleTake {
     my %args = (
-        ErrorsTo    => undef,
         Message     => undef,
         Ticket      => undef,
         Queue       => undef,
@@ -66,10 +65,8 @@ sub HandleTake {
     unless ( $args{Ticket}->Id ) {
         my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
-            To          => $args{ErrorsTo},
             Subject     => "Message not recorded: $args{Subject}",
             Explanation => $error,
-            MIMEObj     => $args{Message}
         );
         FAILURE( $error );
     }
@@ -80,10 +77,8 @@ sub HandleTake {
     return if $status;
 
     MailError(
-        To          => $args{'ErrorsTo'},
         Subject     => "Ticket not taken",
         Explanation => $msg,
-        MIMEObj     => $args{'Message'}
     );
     FAILURE( "Ticket not taken, by email From: $From" );
 }
diff --git a/lib/RT/Interface/Email/Authz/Default.pm b/lib/RT/Interface/Email/Authz/Default.pm
index cdc11f0..5815672 100644
--- a/lib/RT/Interface/Email/Authz/Default.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -56,7 +56,6 @@ with 'RT::Interface::Email::Role';
 
 sub CheckACL {
     my %args = (
-        ErrorsTo    => undef,
         Message     => undef,
         CurrentUser => undef,
         Ticket      => undef,
@@ -127,10 +126,8 @@ EOT
 
 
     MailError(
-        To          => $args{ErrorsTo},
         Subject     => "Permission Denied",
         Explanation => $msg,
-        MIMEObj     => $args{Message},
     );
     FAILURE( $msg );
 }

commit 3539367b2702b1027deb0ec1ed33a2bfe42d86dd
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Mar 6 17:48:21 2014 -0500

    Remove the warning about the deprecated Auth::GnuPG/Auth::SMIME plugins
    
    These plugins were deprecated in 4.2.

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 8529e4c..e9a7433 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -671,22 +671,6 @@ our %META;
             $META{Crypt}{'PostLoadCheck'}->( $self, $self->Get( 'Crypt' ) );
 
             my @plugins = $self->Get('MailPlugins');
-            if ( grep $_ eq 'Auth::GnuPG' || $_ eq 'Auth::SMIME', @plugins ) {
-                $RT::Logger->warning(
-                    'Auth::GnuPG and Auth::SMIME (from an extension) have been'
-                    .' replaced with Auth::Crypt.  @MailPlugins has been adjusted,'
-                    .' but should be updated to replace both with Auth::Crypt to'
-                    .' silence this warning.'
-                );
-                my %seen;
-                @plugins =
-                    grep !$seen{$_}++,
-                    grep {
-                        $_ eq 'Auth::GnuPG' || $_ eq 'Auth::SMIME'
-                        ? 'Auth::Crypt' : $_
-                    } @plugins;
-                $self->Set( MailPlugins => @plugins );
-            }
 
             if ( not @{$self->Get('Crypt')->{Incoming}} and grep $_ eq 'Auth::Crypt', @plugins ) {
                 $RT::Logger->warning("Auth::Crypt enabled in MailPlugins, but no available incoming encryption formats");

commit 384caa6cc2627f5de5a48c28ae3772ebfbfbd349
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Mar 6 18:29:40 2014 -0500

    Allow lazy adding of Auth::MailFrom if no other GetCurrentUser plugins exist
    
    Previously, Auth::MailFrom there were no @MailPlugins.  As mail plugins
    can now provide things other than GetCurrentUser, this causes the odd
    effect where adding the Action::Take plugin suddenly required one to
    specify the Auth::MailFrom plugin as well.
    
    Move to a two-phase setup, in RT::Config, which allows examination of
    the current plugins before deciding to add Auth::MailFrom, based on the
    presence or absence of other GetCurrentUser plugins.

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index e9a7433..7d30e35 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -670,7 +670,11 @@ our %META;
             # Make sure Crypt is post-loaded first
             $META{Crypt}{'PostLoadCheck'}->( $self, $self->Get( 'Crypt' ) );
 
-            my @plugins = $self->Get('MailPlugins');
+            RT::Interface::Email::Plugins(Add => ["Authz::Default", "Action::Defaults"]);
+            RT::Interface::Email::Plugins(Add => ["Auth::MailFrom"])
+                  unless RT::Interface::Email::Plugins(Code => 1, Method => "GetCurrentUser");
+
+            my @plugins = RT::Interface::Email::Plugins();
 
             if ( not @{$self->Get('Crypt')->{Incoming}} and grep $_ eq 'Auth::Crypt', @plugins ) {
                 $RT::Logger->warning("Auth::Crypt enabled in MailPlugins, but no available incoming encryption formats");
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 75a4bd6..700cd11 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -84,9 +84,7 @@ additional options to fine-tune behaviour.
 
 However, note that you B<must> add the
 L<Auth::Crypt|RT::Interface::Email::Auth::Crypt> email filter to enable
-the handling of incoming encrypted/signed messages.  It should be added
-in addition to the standard
-L<Auth::MailFrom|RT::Interface::Email::Auth::Crypt> plugin.
+the handling of incoming encrypted/signed messages.
 
 =head2 %Crypt
 
@@ -132,7 +130,7 @@ will be displayed, as well as options to enable signing and encryption.
 To enable handling of encrypted and signed message in the RT you must
 enable the L<RT::Interface::Email::Auth::Crypt> mail plugin:
 
-    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filter...);
+    Set(@MailPlugins, 'Auth::Crypt', ...other filter...);
 
 =head2 Error handling
 
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 4d0f0bf..0f0a43d 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -240,7 +240,7 @@ option is disabled.
 To enable handling of encrypted and signed message in the RT you should add
 'Auth::Crypt' mail plugin.
 
-    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filter...);
+    Set(@MailPlugins, 'Auth::Crypt', ...other filter...);
 
 See also `perldoc lib/RT/Interface/Email/Auth/Crypt.pm`.
 
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 901301e..b25dbdf 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -240,17 +240,17 @@ sub Gateway {
 
 sub Plugins {
     my %args = (
+        Add => undef,
         Code => 0,
         Method => undef,
         @_
     );
+    state $INIT;
     state @PLUGINS;
 
-    unless (@PLUGINS) {
-        my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
-        push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
-        push @mail_plugins, "Authz::Default";
-        push @mail_plugins, "Action::Defaults";
+    if ($args{Add} or !$INIT) {
+        my @mail_plugins = $INIT ? () : RT->Config->Get('MailPlugins');
+        push @mail_plugins, @{$args{Add}} if $args{Add};
 
         foreach my $plugin (@mail_plugins) {
             if ( ref($plugin) eq "CODE" ) {
@@ -271,6 +271,7 @@ sub Plugins {
                 $RT::Logger->crit( "$plugin - is not class name or code reference");
             }
         }
+        $INIT = 1;
     }
 
     my @list = @PLUGINS;
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 3e07b1f..d42fab1 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -64,10 +64,7 @@ This code is independant from code that encrypts/sign outgoing emails, so
 it's possible to decrypt data without bringing in encryption. To enable
 it put the module in the mail plugins list:
 
-    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filters...);
-
-C<Auth::Crypt> will not function without C<Auth::MailFrom> listed before
-it.
+    Set(@MailPlugins, 'Auth::Crypt', ...other filters...);
 
 =head3 GnuPG
 
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index e955233..b8a8b46 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 
-use RT::Test config => 'Set( @MailPlugins, "Auth::MailFrom", "Action::Take", "Action::Resolve");', tests => undef, actual_server => 1;
+use RT::Test config => 'Set( @MailPlugins, "Action::Take", "Action::Resolve");', tests => undef, actual_server => 1;
 my ($baseurl, $m) = RT::Test->started_ok;
 
 use RT::Tickets;
diff --git a/t/mail/gnupg-bad.t b/t/mail/gnupg-bad.t
index 570501c..00930c9 100644
--- a/t/mail/gnupg-bad.t
+++ b/t/mail/gnupg-bad.t
@@ -10,7 +10,7 @@ use RT::Test::GnuPG
     ),
   };
 
-RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
+RT->Config->Set( 'MailPlugins' => 'Auth::Crypt' );
 
 my ($baseurl, $m) = RT::Test->started_ok;
 

commit b33b8a489d0d005eb69430fca1cfd150a53d4481
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Mar 6 18:32:41 2014 -0500

    There is no reason to not always enable Auth::Crypt
    
    If no encryption protocols are enabled (the default), the Auth::Crypt
    plugin does nothing.  As such, there is no harm to always enabling the
    email plugin -- and doing so removes an additional step to enable
    incoming decryption/verification, making it merely "enable a protocol."

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 08e5e09..906e74c 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2327,9 +2327,7 @@ The following options apply to all cryptography protocols.
 By default, all enabled security protocols will analyze each incoming
 email. You may set C<Incoming> to a subset of this list, if some enabled
 protocols do not apply to incoming mail; however, this is usually
-unnecessary. Note that for any verification or decryption to occur for
-incoming mail, the C<Auth::Crypt> mail plugin must be added to
-L</@MailPlugins> as specified in L<RT::Crypt/Handling incoming messages>.
+unnecessary.
 
 For outgoing emails, the first security protocol from the above list is
 used. Use the C<Outgoing> option to set a security protocol that should
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 7d30e35..484fe3b 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -670,15 +670,9 @@ our %META;
             # Make sure Crypt is post-loaded first
             $META{Crypt}{'PostLoadCheck'}->( $self, $self->Get( 'Crypt' ) );
 
-            RT::Interface::Email::Plugins(Add => ["Authz::Default", "Action::Defaults"]);
+            RT::Interface::Email::Plugins(Add => ["Authz::Default", "Action::Defaults", "Auth::Crypt"]);
             RT::Interface::Email::Plugins(Add => ["Auth::MailFrom"])
                   unless RT::Interface::Email::Plugins(Code => 1, Method => "GetCurrentUser");
-
-            my @plugins = RT::Interface::Email::Plugins();
-
-            if ( not @{$self->Get('Crypt')->{Incoming}} and grep $_ eq 'Auth::Crypt', @plugins ) {
-                $RT::Logger->warning("Auth::Crypt enabled in MailPlugins, but no available incoming encryption formats");
-            }
         },
     },
     Crypt        => {
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 700cd11..e1927ef 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -82,10 +82,6 @@ uses the following config:
 C<Enable> is the only key that is generic for all protocols. A protocol may have
 additional options to fine-tune behaviour.
 
-However, note that you B<must> add the
-L<Auth::Crypt|RT::Interface::Email::Auth::Crypt> email filter to enable
-the handling of incoming encrypted/signed messages.
-
 =head2 %Crypt
 
 This config option hash chooses which protocols are decrypted and
@@ -125,13 +121,6 @@ encrypting by default. As an administrative user of RT, navigate to the
 encryption protocol is enabled, information concerning available keys
 will be displayed, as well as options to enable signing and encryption.
 
-=head2 Handling incoming messages
-
-To enable handling of encrypted and signed message in the RT you must
-enable the L<RT::Interface::Email::Auth::Crypt> mail plugin:
-
-    Set(@MailPlugins, 'Auth::Crypt', ...other filter...);
-
 =head2 Error handling
 
 There are several global templates created in the database by
@@ -252,8 +241,6 @@ sub UseForOutgoing {
 
 Returns the list of encryption protocols that should be used for
 decryption and verification of incoming email; see L<RT_Config/Crypt>.
-This list is irrelevant unless L<RT::Interface::Email::Auth::Crypt> is
-enabled in L<RT_Config/@MailPlugins>.
 
 =cut
 
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 0f0a43d..bef621a 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -93,9 +93,6 @@ Set to true value to enable this subsystem:
         ... other options ...
     );
 
-However, note that you B<must> add the 'Auth::Crypt' email filter to enable
-the handling of incoming encrypted/signed messages.
-
 =head3 Format of outgoing messages
 
 The format of outgoing messages can be controlled using the
@@ -235,15 +232,6 @@ As well, encryption is enabled for autoreplies and other notifications when
 an encypted message enters system via mailgate interface even if queue's
 option is disabled.
 
-=head2 Handling incoming messages
-
-To enable handling of encrypted and signed message in the RT you should add
-'Auth::Crypt' mail plugin.
-
-    Set(@MailPlugins, 'Auth::Crypt', ...other filter...);
-
-See also `perldoc lib/RT/Interface/Email/Auth/Crypt.pm`.
-
 =head2 Encrypting to untrusted keys
 
 Due to limitations of GnuPG, it's impossible to encrypt to an untrusted key,
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index d42fab1..a948aa4 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -60,12 +60,6 @@ RT::Interface::Email::Auth::Crypt - decrypting and verifying protected emails
 This mail plugin decrypts and verifies incoming emails. Supported
 encryption protocols are GnuPG and SMIME.
 
-This code is independant from code that encrypts/sign outgoing emails, so
-it's possible to decrypt data without bringing in encryption. To enable
-it put the module in the mail plugins list:
-
-    Set(@MailPlugins, 'Auth::Crypt', ...other filters...);
-
 =head3 GnuPG
 
 To use the gnupg-secured mail gateway, you need to do the following:
diff --git a/lib/RT/Test/GnuPG.pm b/lib/RT/Test/GnuPG.pm
index e864845..a2ae426 100644
--- a/lib/RT/Test/GnuPG.pm
+++ b/lib/RT/Test/GnuPG.pm
@@ -106,7 +106,6 @@ Set(\%GnuPG, (
     OutgoingMessagesFormat => 'RFC',
 ));
 Set(\%GnuPGOptions => \%{ $dumped_gnupg_options });
-Set(\@MailPlugins => qw(Auth::MailFrom Auth::Crypt));
 };
 
 }
diff --git a/lib/RT/Test/SMIME.pm b/lib/RT/Test/SMIME.pm
index d39c4b4..7c94380 100644
--- a/lib/RT/Test/SMIME.pm
+++ b/lib/RT/Test/SMIME.pm
@@ -101,7 +101,6 @@ sub bootstrap_more_config {
             Keyring => q{$keyring},
             CAPath  => q{$ca},
         );
-        Set(\@MailPlugins => qw(Auth::MailFrom Auth::Crypt));
     };
 
 }
diff --git a/t/mail/gnupg-bad.t b/t/mail/gnupg-bad.t
index 00930c9..a9fd45a 100644
--- a/t/mail/gnupg-bad.t
+++ b/t/mail/gnupg-bad.t
@@ -10,8 +10,6 @@ use RT::Test::GnuPG
     ),
   };
 
-RT->Config->Set( 'MailPlugins' => 'Auth::Crypt' );
-
 my ($baseurl, $m) = RT::Test->started_ok;
 
 $m->login;

commit 59f1c3c67db95efbd3c4dc13402f73cd6cd190d2
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 7 02:17:08 2014 -0500

    Make Crypt not an Auth:: plugin, but hardcoded
    
    Always pushing Auth::Crypt onto plugins causes ordering difficulty -- in
    that BeforeDecode plugins all see undecrypted content.  It is thus
    imposssible for a plugin to to examine decrypted but not decoded
    content.
    
    Move Auth::Crypt, which actually does no auth, to no longer be a plugin,
    but hardcoded into Gateway.

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 484fe3b..4ed8560 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -670,7 +670,7 @@ our %META;
             # Make sure Crypt is post-loaded first
             $META{Crypt}{'PostLoadCheck'}->( $self, $self->Get( 'Crypt' ) );
 
-            RT::Interface::Email::Plugins(Add => ["Authz::Default", "Action::Defaults", "Auth::Crypt"]);
+            RT::Interface::Email::Plugins(Add => ["Authz::Default", "Action::Defaults"]);
             RT::Interface::Email::Plugins(Add => ["Auth::MailFrom"])
                   unless RT::Interface::Email::Plugins(Code => 1, Method => "GetCurrentUser");
         },
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index b25dbdf..319008b 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -52,6 +52,7 @@ use strict;
 use warnings;
 use 5.010;
 
+use RT::Interface::Email::Crypt;
 use Email::Address;
 use MIME::Entity;
 use RT::EmailParser;
@@ -154,6 +155,21 @@ sub Gateway {
     my $SystemQueueObj = RT::Queue->new( RT->SystemUser );
     $SystemQueueObj->Load( $args{'queue'} );
 
+    for my $Code ( Plugins(Method => "BeforeDecrypt") ) {
+        $Code->(
+            Message       => $Message,
+            RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
+        );
+    }
+
+    RT::Interface::Email::Crypt::VerifyDecrypt(
+        Message       => $Message,
+        RawMessageRef => \$args{'message'},
+        Queue         => $SystemQueueObj,
+    );
+
     for my $Code ( Plugins(Method => "BeforeDecode") ) {
         $Code->(
             Message       => $Message,
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Crypt.pm
similarity index 86%
rename from lib/RT/Interface/Email/Auth/Crypt.pm
rename to lib/RT/Interface/Email/Crypt.pm
index a948aa4..7186192 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Crypt.pm
@@ -46,65 +46,25 @@
 #
 # END BPS TAGGED BLOCK }}}
 
-package RT::Interface::Email::Auth::Crypt;
+package RT::Interface::Email::Crypt;
 
 use strict;
 use warnings;
 
 =head1 NAME
 
-RT::Interface::Email::Auth::Crypt - decrypting and verifying protected emails
-
-=head2 DESCRIPTION
-
-This mail plugin decrypts and verifies incoming emails. Supported
-encryption protocols are GnuPG and SMIME.
-
-=head3 GnuPG
-
-To use the gnupg-secured mail gateway, you need to do the following:
-
-Set up a GnuPG key directory with a pubring containing only the keys
-you care about and specify the following in your SiteConfig.pm
-
-    Set(%GnuPGOptions, homedir => '/opt/rt4/var/data/GnuPG');
-
-Read also: L<RT::Crypt> and L<RT::Crypt::GnuPG>.
-
-=head3 SMIME
-
-To use the SMIME-secured mail gateway, you need to do the following:
-
-Set up a SMIME key directory with files containing keys for queues'
-addresses and specify the following in your SiteConfig.pm
-
-    Set(%SMIME,
-        Enable => 1,
-        OpenSSL => '/usr/bin/openssl',
-        Keyring => '/opt/rt4/var/data/smime',
-        CAPath  => '/opt/rt4/var/data/smime/signing-ca.pem',
-        Passphrase => {
-            'queue.address at example.com' => 'passphrase',
-            '' => 'fallback',
-        },
-    );
-
-Read also: L<RT::Crypt> and L<RT::Crypt::SMIME>.
+RT::Interface::Email::Crypt - decrypting and verifying protected emails
 
 =cut
 
 use RT::Crypt;
 use RT::EmailParser ();
 
-use Role::Basic 'with';
-with 'RT::Interface::Email::Role';
-
-sub BeforeDecode {
+sub VerifyDecrypt {
     my %args = (
         Message       => undef,
         RawMessageRef => undef,
         Queue         => undef,
-        Actions       => undef,
         @_
     );
 

commit 9af33601c6e4855e2302adc01bc4422bc980ebd1
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Mar 11 16:58:41 2014 -0400

    Move RejectOnUnencrypted to being a mail plugin

diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
index fe53e64..42e794f 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -48,6 +48,12 @@ The C<$UnsafeEmailCommands> option has been replaced with two mail
 plugins, L<RT::Interface::Email::Action::Take>, and
 L<RT::Interface::Email::Action::Resolve>.
 
+=item *
+
+The C<RejectOnUnencrypted> option to L<RT_Config/%Crypt> has been
+replaced with a mail plugin,
+L<RT::Interface::Email::Authz::RequireEncrypted>.
+
 =back
 
 =cut
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 906e74c..9b07e70 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2334,9 +2334,6 @@ used. Use the C<Outgoing> option to set a security protocol that should
 be used in outgoing emails.  At this moment, only one protocol can be
 used to protect outgoing emails.
 
-Set C<RejectOnUnencrypted> to 1 if all incoming email must be
-properly encrypted.  All unencrypted emails will be rejected by RT.
-
 Set C<RejectOnMissingPrivateKey> to 0 if you don't want to reject
 emails encrypted for key RT doesn't have and can not decrypt.
 
@@ -2358,7 +2355,6 @@ Set( %Crypt,
     Incoming                  => undef, # ['GnuPG', 'SMIME']
     Outgoing                  => undef, # 'SMIME'
 
-    RejectOnUnencrypted       => 0,
     RejectOnMissingPrivateKey => 1,
     RejectOnBadData           => 1,
 
diff --git a/lib/RT/Interface/Email/Authz/RequireEncrypted.pm b/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
new file mode 100644
index 0000000..b96cd2c
--- /dev/null
+++ b/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
@@ -0,0 +1,80 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at 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., 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:
+#
+# (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
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# 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::Authz::RequireEncrypted;
+
+use strict;
+use warnings;
+
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
+sub BeforeDecode {
+    my %args = (
+        Action      => undef,
+        Message     => undef,
+        CurrentUser => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    my $encryption = $args{Message}->head->get('X-RT-Incoming-Encryption');
+    chomp $encryption;
+    return unless $encryption eq "Not encrypted";
+
+    RT::Interface::Email::Crypt::EmailErrorToSender(
+        %args,
+        Template  => 'Error: unencrypted message',
+        Arguments => { Message  => $args{'Message'} },
+    );
+    $RT::Logger->warning("rejected because the message is unencrypted");
+    FAILURE('rejected because the message is unencrypted');
+}
+
+1;
diff --git a/lib/RT/Interface/Email/Crypt.pm b/lib/RT/Interface/Email/Crypt.pm
index 7186192..e44ba9b 100644
--- a/lib/RT/Interface/Email/Crypt.pm
+++ b/lib/RT/Interface/Email/Crypt.pm
@@ -85,20 +85,9 @@ sub VerifyDecrypt {
         Entity => $args{'Message'},
     );
     if ( !@res ) {
-        if (RT->Config->Get('Crypt')->{'RejectOnUnencrypted'}) {
-            EmailErrorToSender(
-                %args,
-                Template  => 'Error: unencrypted message',
-                Arguments => { Message  => $args{'Message'} },
-            );
-            $RT::Logger->warning("rejected because the message is unencrypted with RejectOnUnencrypted enabled");
-            FAILURE('rejected because the message is unencrypted with RejectOnUnencrypted enabled');
-        }
-        else {
-            $args{'Message'}->head->replace(
-                'X-RT-Incoming-Encryption' => 'Not encrypted'
-            );
-        }
+        $args{'Message'}->head->replace(
+            'X-RT-Incoming-Encryption' => 'Not encrypted'
+        );
         return;
     }
 
diff --git a/t/mail/smime/reject_on_unencrypted.t b/t/mail/smime/reject_on_unencrypted.t
index ab62d83..60223ea 100644
--- a/t/mail/smime/reject_on_unencrypted.t
+++ b/t/mail/smime/reject_on_unencrypted.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef, actual_server => 1, config => 'Set( %Crypt, RejectOnUnencrypted => 1 );';
+use RT::Test::SMIME tests => undef, actual_server => 1, config => 'Set( @MailPlugins, "Authz::RequireEncrypted" );';
 my $test = 'RT::Test::SMIME';
 
 use IPC::Run3 'run3';

commit 65d32d435937f29abe2e3f5493be85938ef6b77b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 7 19:34:18 2014 -0500

    Merge ParseAddressFromHeader and RT::EmailParser->ParseEmailAddress
    
    RT::Interface::Email's ParseAddressFromHeader was merely a thin shim
    around RT::EmailParser->ParseEmailAddress, with the tiny addition of
    removing doubled quotes -- which, according to d5b74f19, had been seen
    in the wild.  Move that additional 'feature' into
    RT::EmailParser->ParseEmailAddress, and switch all callsites of
    ParseAddressFromHeader to call that instead.

diff --git a/lib/RT/EmailParser.pm b/lib/RT/EmailParser.pm
index 0ca3e4b..6f1a74e 100644
--- a/lib/RT/EmailParser.pm
+++ b/lib/RT/EmailParser.pm
@@ -497,6 +497,9 @@ sub ParseEmailAddress {
     my $self = shift;
     my $address_string = shift;
 
+    # Some broken mailers send:  ""Vincent, Jesse"" <jesse at fsck.com>. Hate
+    $address_string =~ s/\"\"(.*?)\"\"/\"$1\"/g;
+
     my @list = Email::Address::List->parse(
         $address_string,
         skip_comments => 1,
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 319008b..2e9ab7f 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -403,15 +403,12 @@ to investigate the parse failure.
 
 sub ParseSenderAddressFromHead {
     my $head = shift;
-    my @sender_headers = ('Reply-To', 'From', 'Sender');
     my @errors;  # Accumulate any errors
 
-    #Figure out who's sending this message.
-    foreach my $header ( @sender_headers ) {
+    foreach my $header ( 'Reply-To', 'From', 'Sender' ) {
         my $addr_line = Encode::decode( "UTF-8", $head->get($header) ) || next;
-        my ($addr, $name) = ParseAddressFromHeader( $addr_line );
-        # only return if the address is not empty
-        return ($addr, $name, @errors) if $addr;
+        my ($addr) = RT::EmailParser->ParseEmailAddress( $addr_line );
+        return ($addr->address, $addr->phrase, @errors) if $addr;
 
         chomp $addr_line;
         push @errors, "$header: $addr_line";
@@ -431,44 +428,15 @@ From:, Sender)
 sub ParseErrorsToAddressFromHead {
     my $head = shift;
 
-    #Figure out who's sending this message.
-
     foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
+        my $value = Encode::decode( "UTF-8", $head->get($header) );
+        next unless $value;
 
-        # If there's a header of that name
-        my $headerobj = Encode::decode( "UTF-8", $head->get($header) );
-        if ($headerobj) {
-            my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
-
-            # If it's got actual useful content...
-            return ($addr) if ($addr);
-        }
+        my ( $email ) = RT::EmailParser->ParseEmailAddress($value);
+        return $email->address if $email;
     }
 }
 
-
-
-=head3 ParseAddressFromHeader ADDRESS
-
-Takes an address from C<$head->get('Line')> and returns a tuple: user at host, friendly name
-
-=cut
-
-sub ParseAddressFromHeader {
-    my $Addr = shift;
-
-    # Some broken mailers send:  ""Vincent, Jesse"" <jesse at fsck.com>. Hate
-    $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;
-    my @Addresses = RT::EmailParser->ParseEmailAddress($Addr);
-
-    my ($AddrObj) = grep ref $_, @Addresses;
-    unless ( $AddrObj ) {
-        return ( undef, undef );
-    }
-
-    return ( $AddrObj->address, $AddrObj->phrase );
-}
-
 =head3 _HandleMachineGeneratedMail
 
 Takes named params:
diff --git a/lib/RT/User.pm b/lib/RT/User.pm
index 9262860..b614070 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -524,7 +524,8 @@ sub LoadOrCreateByEmail {
     } elsif ( UNIVERSAL::isa( $_[0] => 'Email::Address' ) ) {
         @create{'EmailAddress','RealName'} = ($_[0]->address, $_[0]->phrase);
     } else {
-        @create{'EmailAddress','RealName'} = RT::Interface::Email::ParseAddressFromHeader( $_[0] );
+        my ($addr) = RT::EmailParser->ParseEmailAddress( $_[0] );
+        @create{'EmailAddress','RealName'} = $addr ? ($addr->address, $addr->phrase) : (undef, undef);
     }
 
     $self->LoadByEmail( $create{EmailAddress} );
diff --git a/t/api/emailparser.t b/t/api/emailparser.t
index a58cb36..d4f15db 100644
--- a/t/api/emailparser.t
+++ b/t/api/emailparser.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 11;
+use RT::Test nodb => 1, tests => undef;
 
 ok(require RT::EmailParser);
 
@@ -19,20 +19,20 @@ my @after = ("frt\@example.com");
 ok(eq_array(RT::EmailParser->CullRTAddresses(@before), at after), "CullRTAddresses only culls RT addresses");
 
 {
-    require RT::Interface::Email;
-    my ( $addr, $name ) =
-      RT::Interface::Email::ParseAddressFromHeader('foo at example.com');
-    is( $addr, 'foo at example.com', 'addr for foo at example.com' );
-    is( $name, undef,             'no name for foo at example.com' );
-
-    ( $addr, $name ) =
-      RT::Interface::Email::ParseAddressFromHeader('Foo <foo at example.com>');
-    is( $addr, 'foo at example.com', 'addr for Foo <foo at example.com>' );
-    is( $name, 'Foo',             'name for Foo <foo at example.com>' );
-
-    ( $addr, $name ) =
-      RT::Interface::Email::ParseAddressFromHeader('foo at example.com (Comment)');
-    is( $addr, 'foo at example.com', 'addr for foo at example.com (Comment)' );
-    is( $name, undef,             'no name for foo at example.com (Comment)' );
+    my ( $addr ) =
+      RT::EmailParser->ParseEmailAddress('foo at example.com');
+    is( $addr->address, 'foo at example.com', 'addr for foo at example.com' );
+    is( $addr->phrase,  undef,             'no name for foo at example.com' );
+
+    ( $addr ) =
+      RT::EmailParser->ParseEmailAddress('Foo <foo at example.com>');
+    is( $addr->address, 'foo at example.com', 'addr for Foo <foo at example.com>' );
+    is( $addr->phrase,  'Foo',             'name for Foo <foo at example.com>' );
+
+    ( $addr ) =
+      RT::EmailParser->ParseEmailAddress('foo at example.com (Comment)');
+    is( $addr->address, 'foo at example.com', 'addr for foo at example.com (Comment)' );
+    is( $addr->phrase,  undef,             'no name for foo at example.com (Comment)' );
 }
 
+done_testing;

commit 8c71c0f7b799677c530519591d9592506f100cea
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 7 19:35:39 2014 -0500

    Fix callsites of ParseSenderAddressFromHead to be slightly less incomprehensible

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 2e9ab7f..b895880 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -495,7 +495,7 @@ sub IsMachineGeneratedMail {
     # can punt the logic of "what to do when we get a bounce"
     # to the scrip. We might want to notify nobody. Or just
     # the RT Owner. Or maybe all Privileged watchers.
-    my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
+    my ( $Sender ) = ParseSenderAddressFromHead($head);
     $head->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
     $head->replace( 'RT-DetectedAutoGenerated', 'true' );
 
@@ -547,7 +547,7 @@ sub CheckForSuspiciousSender {
 
     #TODO: search through the whole email and find the right Ticket ID.
 
-    my ( $From, $junk ) = ParseSenderAddressFromHead($head);
+    my ( $From ) = ParseSenderAddressFromHead($head);
 
     # If unparseable (non-ASCII), $From can come back undef
     return undef if not defined $From;
diff --git a/lib/RT/Interface/Email/Crypt.pm b/lib/RT/Interface/Email/Crypt.pm
index e44ba9b..fd37e5d 100644
--- a/lib/RT/Interface/Email/Crypt.pm
+++ b/lib/RT/Interface/Email/Crypt.pm
@@ -215,7 +215,7 @@ sub EmailErrorToSender {
     $args{'Arguments'} ||= {};
     $args{'Arguments'}{'TicketObj'} ||= $args{'Ticket'};
 
-    my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
+    my ($address) = RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head );
     my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
         To        => $address,
         Template  => $args{'Template'},

commit b73528481f2e556462c2b326c0d15779ef734e7e
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 7 19:40:54 2014 -0500

    $MessageId is only used in IsMachineGeneratedMail; move it in there
    
    This also makes clear that the bugus no-message-id fallback is
    unnecessary -- as we only use $MessageId in cases of loops from RT, and
    RT is known to set a message-id.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index b895880..c387a78 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -187,9 +187,6 @@ sub Gateway {
     my $From = Encode::decode( "UTF-8", $head->get("From") );
     chomp $From if defined $From;
 
-    my $MessageId = Encode::decode( "UTF-8", $head->get('Message-ID') )
-        || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
-
     #Pull apart the subject line
     my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || '');
     chomp $Subject;
@@ -200,7 +197,6 @@ sub Gateway {
         if IsMachineGeneratedMail(
             Message   => $Message,
             Subject   => $Subject,
-            MessageId => $MessageId,
         );
 
     # Make all errors from here on out bounce back to $ErrorsTo
@@ -454,7 +450,6 @@ sub IsMachineGeneratedMail {
     my %args = (
         Message => undef,
         Subject => undef,
-        MessageId => undef,
         @_
     );
     my $head = $args{'Message'}->head;
@@ -475,7 +470,8 @@ sub IsMachineGeneratedMail {
 
     # Warn someone if it's a loop, before we drop it on the ground
     if ($IsALoop) {
-        $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
+        my $MessageId = Encode::decode( "UTF-8", $head->get('Message-ID') );
+        $RT::Logger->crit("RT Received mail ($MessageId) from itself.");
 
         #Should we mail it to RTOwner?
         if ( RT->Config->Get('LoopsToRTOwner') ) {

commit f71f2575ff7ea2468fa77d45191ee6b526432945
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 7 19:47:18 2014 -0500

    Merge CheckForSuspiciousSender, CheckForAutoGenerated, and CheckForBounce
    
    There is no reason for the proliferation of methods, as they are used
    identically.  Merge them into one CheckForAutoGenerated method.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index c387a78..0df245d 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -454,9 +454,7 @@ sub IsMachineGeneratedMail {
     );
     my $head = $args{'Message'}->head;
 
-    my $IsBounce = CheckForBounce($head);
     my $IsAutoGenerated = CheckForAutoGenerated($head);
-    my $IsSuspiciousSender = CheckForSuspiciousSender($head);
     my $IsALoop = CheckForLoops($head);
 
     my $owner_mail = RT->Config->Get('OwnerEmail');
@@ -466,7 +464,7 @@ sub IsMachineGeneratedMail {
 
     # If the message is autogenerated, we need to know, so we can not
     # send mail to the sender
-    return unless $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop;
+    return unless $IsAutoGenerated || $IsALoop;
 
     # Warn someone if it's a loop, before we drop it on the ground
     if ($IsALoop) {
@@ -521,48 +519,10 @@ sub CheckForLoops {
     return undef;
 }
 
-=head3 CheckForSuspiciousSender HEAD
-
-Takes a HEAD object of L<MIME::Head> class and returns true if sender
-is suspicious. Suspicious means mailer daemon.
-
-See also L</ParseSenderAddressFromHead>.
-
-=cut
-
-sub CheckForSuspiciousSender {
-    my $head = shift;
-
-    #if it's from a postmaster or mailer daemon, it's likely a bounce.
-
-    #TODO: better algorithms needed here - there is no standards for
-    #bounces, so it's very difficult to separate them from anything
-    #else.  At the other hand, the Return-To address is only ment to be
-    #used as an error channel, we might want to put up a separate
-    #Return-To address which is treated differently.
-
-    #TODO: search through the whole email and find the right Ticket ID.
-
-    my ( $From ) = ParseSenderAddressFromHead($head);
-
-    # If unparseable (non-ASCII), $From can come back undef
-    return undef if not defined $From;
-
-    if (   ( $From =~ /^mailer-daemon\@/i )
-        or ( $From =~ /^postmaster\@/i )
-        or ( $From eq "" ))
-    {
-        return (1);
-
-    }
-
-    return undef;
-}
-
 =head3 CheckForAutoGenerated HEAD
 
 Takes a HEAD object of L<MIME::Head> class and returns true if message is
-autogenerated. Checks C<Precedence>, C<Auto-Submitted>, and
+autogenerated. Checks C<Precedence>, RFC3834 C<Auto-Submitted>, and
 C<X-FC-Machinegenerated> fields of the head in tests.
 
 =cut
@@ -570,32 +530,29 @@ C<X-FC-Machinegenerated> fields of the head in tests.
 sub CheckForAutoGenerated {
     my $head = shift;
 
-    if (grep { /^(bulk|junk)/i } $head->get_all("Precedence")) {
-        return (1);
-    }
+    # Bounces, via return-path
+    my $ReturnPath = $head->get("Return-path") || "";
+    return 1 if $ReturnPath =~ /<>/;
+
+    # Bounces, via mailer-daemon or postmaster
+    my ( $From ) = ParseSenderAddressFromHead($head);
+    return 1 if defined $From and $From =~ /^mailer-daemon\@/i;
+    return 1 if defined $From and $From =~ /^postmaster\@/i;
+    return 1 if defined $From and $From eq "";
+
+    # Bulk or junk messages are auto-generated
+    return 1 if grep {/^(bulk|junk)/i} $head->get_all("Precedence");
 
     # Per RFC3834, any Auto-Submitted header which is not "no" means
     # it is auto-generated.
     my $AutoSubmitted = $head->get("Auto-Submitted") || "";
-    if ( length $AutoSubmitted and $AutoSubmitted ne "no" ) {
-        return (1);
-    }
+    return 1 if length $AutoSubmitted and $AutoSubmitted ne "no";
 
     # First Class mailer uses this as a clue.
     my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
-    if ( $FCJunk =~ /^true/i ) {
-        return (1);
-    }
-
-    return (0);
-}
+    return 1 if $FCJunk =~ /^true/i;
 
-
-sub CheckForBounce {
-    my $head = shift;
-
-    my $ReturnPath = $head->get("Return-path") || "";
-    return ( $ReturnPath =~ /<>/ );
+    return 0;
 }
 
 =head2 ExtractTicketId

commit b1ea3555e7c3a59fbabb01646e9b2bebe870579f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Mar 11 17:05:13 2014 -0400

    Reduce repetition by making MailError handle throwing the FAILURE, as well

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 0df245d..c0641b0 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -143,11 +143,8 @@ sub Gateway {
         MailError(
             Subject     => "RT Bounce: Unparseable message",
             Explanation => "RT couldn't process the message below",
-            Attach      => $args{'message'}
-        );
-
-        FAILURE(
-            "Failed to parse this message. Something is likely badly wrong with the message"
+            Attach      => $args{'message'},
+            FAILURE     => 1,
         );
     }
 
@@ -322,8 +319,8 @@ sub GetCurrentUser {
     MailError(
         Subject     => "Permission Denied",
         Explanation => "You do not have permission to communicate with RT",
+        FAILURE     => 1,
     );
-    FAILURE("Could not load a valid user");
 }
 
 =head2 CheckACL
@@ -360,8 +357,8 @@ sub CheckACL {
     MailError(
         Subject     => "Permission Denied",
         Explanation => "You have no permission to $args{Action}",
+        FAILURE     => 1,
     );
-    FAILURE( "You have no permission to $args{Action}" );
 }
 
 sub HandleAction {
@@ -643,6 +640,7 @@ sub MailError {
         MIMEObj     => undef,
         Attach      => undef,
         LogLevel    => 'crit',
+        FAILURE     => 0,
         @_
     );
 
@@ -686,6 +684,8 @@ sub MailError {
     }
 
     SendEmail( Entity => $entity, Bounce => 1 );
+
+    FAILURE( "$args{Subject}: $args{Explanation}" ) if $args{FAILURE};
 }
 
 =head2 SENDING EMAIL
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
index 105b356..e2e3504 100644
--- a/lib/RT/Interface/Email/Action/Defaults.pm
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -97,8 +97,8 @@ sub _HandleCreate {
     MailError(
         Subject     => "Ticket creation failed: $args{Subject}",
         Explanation => $ErrStr,
+        FAILURE     => 1,
     );
-    FAILURE("Ticket creation failed: $ErrStr", $args{Ticket} );
 }
 
 sub HandleComment {
@@ -123,12 +123,11 @@ sub _HandleEither {
     return _HandleCreate(@_) unless $args{TicketId};
 
     unless ( $args{Ticket}->Id ) {
-        my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
             Subject     => "Message not recorded: $args{Subject}",
-            Explanation => $error,
+            Explanation => "Could not find a ticket with id " . $args{TicketId},
+            FAILURE     => 1,
         );
-        FAILURE( $error );
     }
 
     my $action = ucfirst $args{Action};
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
index daaa3dd..0ffa936 100644
--- a/lib/RT/Interface/Email/Action/Resolve.pm
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -63,12 +63,11 @@ sub HandleResolve {
     );
 
     unless ( $args{Ticket}->Id ) {
-        my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
             Subject     => "Message not recorded: $args{Subject}",
-            Explanation => $error,
+            Explanation => "Could not find a ticket with id " . $args{TicketId},
+            FAILURE     => 1,
         );
-        FAILURE( $error );
     }
 
     my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
@@ -83,8 +82,8 @@ sub HandleResolve {
     MailError(
         Subject     => "Ticket not resolved",
         Explanation => $msg,
+        FAILURE     => 1,
     );
-    FAILURE( "Ticket not resolved, by email From: $From" );
 }
 
 1;
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
index 60b9b15..4c599ca 100644
--- a/lib/RT/Interface/Email/Action/Take.pm
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -63,12 +63,11 @@ sub HandleTake {
     );
 
     unless ( $args{Ticket}->Id ) {
-        my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
             Subject     => "Message not recorded: $args{Subject}",
-            Explanation => $error,
+            Explanation => "Could not find a ticket with id " . $args{TicketId},
+            FAILURE     => 1,
         );
-        FAILURE( $error );
     }
 
     my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
@@ -79,8 +78,8 @@ sub HandleTake {
     MailError(
         Subject     => "Ticket not taken",
         Explanation => $msg,
+        FAILURE     => 1,
     );
-    FAILURE( "Ticket not taken, by email From: $From" );
 }
 
 1;
diff --git a/lib/RT/Interface/Email/Authz/Default.pm b/lib/RT/Interface/Email/Authz/Default.pm
index 5815672..a364bb2 100644
--- a/lib/RT/Interface/Email/Authz/Default.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -128,8 +128,8 @@ EOT
     MailError(
         Subject     => "Permission Denied",
         Explanation => $msg,
+        FAILURE     => 1,
     );
-    FAILURE( $msg );
 }
 
 1;

commit a008d0a07a3af1c3aded25bd623923dcff659c75
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Mar 11 17:14:08 2014 -0400

    Move ACL checking for Take and Resolve into their own plugins

diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
index 0ffa936..b0fdaf7 100644
--- a/lib/RT/Interface/Email/Action/Resolve.pm
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -54,6 +54,39 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+sub CheckACL {
+    my %args = (
+        Message     => undef,
+        CurrentUser => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        Action      => undef,
+        @_,
+    );
+
+    return unless lc $args{Action} eq "resolve";
+
+    unless ( $args{Ticket}->Id ) {
+        MailError(
+            Subject     => "Message not recorded: $args{Subject}",
+            Explanation => "Could not find a ticket with id $args{TicketId}",
+            FAILURE     => 1,
+        );
+    }
+
+    my $principal = $args{CurrentUser}->PrincipalObj;
+    return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' );
+
+    my $email = $args{CurrentUser}->UserObj->EmailAddress;
+    my $qname = $args{Queue}->Name;
+    my $tid   = $args{Ticket}->id;
+    MailError(
+        Subject     => "Permission Denied",
+        Explanation => "$email has no right to own ticket $tid in queue $qname",
+        FAILURE     => 1,
+    );
+}
+
 sub HandleResolve {
     my %args = (
         Message     => undef,
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
index 4c599ca..e18d439 100644
--- a/lib/RT/Interface/Email/Action/Take.pm
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -54,22 +54,47 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
-sub HandleTake {
+sub CheckACL {
     my %args = (
         Message     => undef,
+        CurrentUser => undef,
         Ticket      => undef,
         Queue       => undef,
+        Action      => undef,
         @_,
     );
 
+    return unless lc $args{Action} eq "take";
+
     unless ( $args{Ticket}->Id ) {
         MailError(
             Subject     => "Message not recorded: $args{Subject}",
-            Explanation => "Could not find a ticket with id " . $args{TicketId},
+            Explanation => "Could not find a ticket with id $args{TicketId}",
             FAILURE     => 1,
         );
     }
 
+    my $principal = $args{CurrentUser}->PrincipalObj;
+    return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' );
+
+    my $email = $args{CurrentUser}->UserObj->EmailAddress;
+    my $qname = $args{Queue}->Name;
+    my $tid   = $args{Ticket}->id;
+    MailError(
+        Subject     => "Permission Denied",
+        Explanation => "$email has no right to own ticket $tid in queue $qname",
+        FAILURE     => 1,
+    );
+}
+
+sub HandleTake {
+    my %args = (
+        Message     => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
     my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
 
     my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{Ticket}->CurrentUser->id );
diff --git a/lib/RT/Interface/Email/Authz/Default.pm b/lib/RT/Interface/Email/Authz/Default.pm
index a364bb2..545e23f 100644
--- a/lib/RT/Interface/Email/Authz/Default.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -90,14 +90,6 @@ might need to grant 'Everyone' the ReplyToTicket right.
 EOT
             );
         }
-        elsif ( $args{'Action'} =~ /^take$/i ) {
-            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' );
-            $msg = "$email has no right to own ticket $tid in queue $qname";
-        }
-        elsif ( $args{'Action'} =~ /^resolve$/i ) {
-            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' );
-            $msg = "$email has no right to resolve ticket $tid in queue $qname";
-        }
         else {
             $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
             return;

commit 140d5c84e757692692b13803b058c74783a45346
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 7 02:17:30 2014 -0500

    Update POD for new methods and functionality

diff --git a/docs/extending/mail_plugins.pod b/docs/extending/mail_plugins.pod
index ea43e2e..ce29d4f 100644
--- a/docs/extending/mail_plugins.pod
+++ b/docs/extending/mail_plugins.pod
@@ -1,67 +1,250 @@
 =head1 MAIL PLUGINS
 
 By default, the mail gateway will accept mail from anyone. However,
-there are situations in which you will want to authenticate users
-before allowing them to communicate with the system. You can do this
-via a plug-in mechanism in the RT configuration.
-
-You can set the array C<@MailPlugins> to be a list of plugins. The
-default plugin, if this is not given, is C<Auth::MailFrom> - that is,
-authentication of the person is done based on the C<From> header of the
-email. If you have additional filters or authentication mechanisms, you
-can list them here and they will be called in order:
-
-    Set( @MailPlugins =>
-        "RT::Extension::SpamAssassin",
-        "Auth::Crypt",
-        "Auth::MailFrom",
+there are situations in which you will want to authenticate users before
+allowing them to communicate with the system.  You can do this via a
+plug-in mechanism in the RT configuration.
+
+You can set the array C<@MailPlugins> to be a list of plugins. For
+example, to allow the mailgate to specify a 'take' action, you can use
+the bundled L<RT::Interface::Email::Actions::Take>:
+
+    Set( @MailPlugins,
+        "Actions::Take",
     );
 
-See the documentation for any additional plugins you have.
+Anonymous subroutine references found in C<@MailPlugins> are treated
+like L</GetCurrentUser> methods.
+
+
+=head2 Core plugins
+
+A small number of plugins are included with core RT, but not enabled by
+default:
+
+=over
+
+=item L<RT::Interface::Email::Actions::Take>
+
+Allows the mailgate to specify C<--action take-comment>, for instance,
+which would take the ticket before commenting.  This action is somewhat
+"unsafe," which is why it is not enabled by default.  It can also often
+be accomplished via a scrip instead.
+
+=item L<RT::Interface::Email::Actions::Resolve>
+
+Allows the mailgate to specify C<--action correspond-resolve>, for
+instance, which would correspond, then resolve the ticket.  This action
+is somewhat "unsafe," which is why it is not enabled by default.  It can
+also often be accomplished via a scrip instead.
+
+=item L<RT::Interface::Email::Authz::RequireEncrypted>
+
+Forces that all incoming mail be encrypted to be accepted.
+
+=back
 
 You may also put Perl subroutines into the C<@MailPlugins> array, if
 they behave as described below.
 
 =head1 WRITING PLUGINS
 
-What's actually going on in the above is that C<@MailPlugins> is a
-list of Perl modules; RT prepends C<RT::Interface::Email::> to the name,
-to form a package name, and then C<use>'s this module. The module is
-expected to provide a C<GetCurrentUser> subroutine, which takes a hash of
-several parameters:
+C<@MailPlugins> is a list of Perl modules; RT prepends
+C<RT::Interface::Email::> to the name to form a package name, and then
+C<use>'s this module.  The module should implement
+L<RT::Interface::Email::Role>.
+
+=head2 Plugin methods
+
+L<RT::Interface::Email::Role> defines a number of functions which are
+useful for immediately aborting processing.  They include
+L<RT::Interface::Email::Role/SUCCESS>,
+L<RT::Interface::Email::Role/FAILURE>, and
+L<RT::Interface::Email::Role/TMPFAIL>; read their descriptions for
+information on how to immediately abort processing from mail plugins.
+
+=head2 Plugin hooks
+
+Mail plugins are expected to provide one or more of the following
+methods:
 
-=over 4
+=head3 BeforeDecrypt
+
+Called before the message is decoded or decrypted.  Its return value is
+ignored; it is passed the following parameters:
+
+=over
 
 =item Message
 
-A C<MIME::Entity> object representing the email
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
 
-=item CurrentUser
+=item RawMessage
 
-An C<RT::CurrentUser> object
+A reference to the string containing the original message.  This should
+not be modified.
 
-=item AuthStat
+=item Queue
 
-The authentication level returned from the previous plugin.
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
 
-=item Ticket [OPTIONAL]
+=item Actions
 
-The ticket under discussion
+An array reference of actions to perform; the C<--action> argument which
+was passed to L<rt-mailgate>.  This may be modified.
+
+=back
+
+
+=head3 BeforeDecode
+
+Called after the message has been decrypted and verified, but before the
+bodies have been decoded of their content transfer encoding.  Its return
+value is ignored; it is passed the following parameters:
+
+=over
+
+=item Message
+
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
+
+=item RawMessage
+
+A reference to the string containing the original message.  This should
+not be modified.
+
+=item Queue
+
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
+
+=item Actions
+
+An array reference of actions to perform; the C<--action> argument which
+was passed to L<rt-mailgate>.  This may be modified.
+
+=back
+
+
+
+=head3 GetCurrentUser
+
+This method is called in order on the mail plugins that define it.  The
+first method to return a L<RT::CurrentUser> value shortcuts all other
+plugins.  It is passed:
+
+=over
+
+=item Message
 
-=item Queue [OPTIONAL]
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
 
-If we don't already have a ticket id, we need to know which queue we're talking about
+=item RawMessage
+
+A reference to the string containing the original message.  This should
+not be modified.
+
+=item Ticket
+
+A L<RT::Ticket>, the ticket (if any) that has been extracted from the
+subject.  If there was no ticket id, this value will be a L<RT::Ticket>
+object with no C<id>.
+
+=item Queue
+
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
+
+=back
+
+
+=head3 CheckACL
+
+Called to determine authorization -- namely, can the current user
+complete the action in question?  While RT's standard permission
+controls apply, this allows a better error message, or more limited
+restrictions on the email gateway.
+
+Only the I<first> action (if there are more than one defined) is
+checked, as the process of completing the first action might affect the
+later actions; consider the case of C<take-correspond>, where the
+C<correspond> action might only be avilable to owners.
+
+Each plugin defining this method is called in turn; as soon as one
+plugin returns true, the rest are short-circuited.  Arguments include:
+
+=over
+
+=item Message
+
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
+
+=item CurrentUser
+
+A L<RT::CurrentUser> object representing the authenticated user.
 
 =item Action
 
-The action being performed. At the moment, it's one of "comment" or "correspond"
+A string representing the action to be undertaken.
+
+=item Ticket
+
+A L<RT::Ticket>, the ticket (if any) that has been extracted from the
+subject.  If there was no ticket id, this value will be a L<RT::Ticket>
+object with no C<id>.
+
+=item Queue
+
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
+
+=back
+
+
+=head3 HandleI<Action>
+
+For any given action I<foo>, the presence of a subroutine called
+C<HandleFoo> signals the ability of the mailgate to handle that action.
+The first plugin in to define the method is called, and its return value
+ignored.  It is passed:
+
+=over
+
+=item Message
+
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
+
+=item Subject
+
+A string, the original C<Subject> header of the message before it was
+modified to extract the ticket id.
+
+=item CurrentUser
+
+A L<RT::CurrentUser> object representing the authenticated user.
+
+=item Ticket
+
+A L<RT::Ticket>, the ticket (if any) that has been extracted from the
+subject.  If there was no ticket id, this value will be a L<RT::Ticket>
+object with no C<id>.
+
+=item TicketId
+
+The value id that was extracted from the subject; this allows a
+non-existant ticket id to be differentiated from no subject id, as both
+will present as having an unloaded C<Ticket> argument.
+
+=item Queue
+
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
 
 =back
 
-It returns two values, the new C<RT::CurrentUser> object, and the new
-authentication level. The authentication level can be zero, not allowed
-to communicate with RT at all, (a "permission denied" error is mailed to
-the correspondent) or one, which is the normal mode of operation.
-Additionally, if C<-1> is returned, then the processing of the plug-ins
-stops immediately and the message is ignored.
+=head1 SEE ALSO
+
+L<RT::Interface::Email::Role>
 
+=cut
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index c0641b0..522a33b 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -67,7 +67,7 @@ use 5.010;
 
 =head1 NAME
 
-  RT::Interface::Email - helper functions for parsing email sent to RT
+  RT::Interface::Email - helper functions for parsing and sending email
 
 =head1 METHODS
 
@@ -77,32 +77,41 @@ use 5.010;
 
 Takes parameters:
 
-    action
-    queue
-    message
+=over
 
+=item C<action>
 
-This performs all the "guts" of the mail rt-mailgate program, and is
-designed to be called from the web interface with a message, user
-object, and so on.
+A C<-> separated list of actions to run.  Standard actions, as detailed
+in L<bin/rt-mailgate>, are C<comment> and C<correspond>.  The
+L<RT::Interface::Email::Actions::Take> and
+L<RT::Interface::Email::Actions::Resolve> plugins can be added to
+L<RT_Config/@MailPlugins> to provide C<take> and C<resolve> actions,
+respectively.
 
-Can also take an optional 'ticket' parameter; this ticket id overrides
-any ticket id found in the subject.
+=item C<queue>
 
-Returns:
+The queue that tickets should be created in, if no ticket id is found on
+the message.  Can be either a name or an id; defaults to 1.
 
-    An array of:
+=item C<message>
 
-    (status code, message, optional ticket object)
+The content of the message, as obtained from the MTA.
 
-    status code is a numeric value.
+=item C<ticket>
 
-      for temporary failures, the status code should be -75
+Optional; this ticket id overrides any ticket number derived from the
+subject.
 
-      for permanent failures which are handled by RT, the status code
-      should be 0
+=back
+
+Secrypts and verifies the message, decodes the transfer encoding,
+determines the user that the mail was sent from, and performs the given
+actions.
 
-      for succces, the status code should be 1
+Returns a list of C<(status, message, ticket)>.  The C<status> is -75
+for a temporary failure (to be retried later bt the MTA), 0 for a
+permanent failure which did not result in a ticket, and 1 for a ticket
+that was found and acted on.
 
 =cut
 
@@ -247,6 +256,14 @@ sub Gateway {
     SUCCESS( $Ticket );
 }
 
+=head3 Plugins Method => C<name>, Code => 0
+
+Returns the list of subroutine references for the given method C<name>
+from the configured L<RT_Config/@MailPlugins>.  If C<Code> is passed a
+true value, includes anonymous subroutines found in C<@MailPlugins>.
+
+=cut
+
 sub Plugins {
     my %args = (
         Add => undef,
@@ -289,7 +306,21 @@ sub Plugins {
     return @list;
 }
 
-=head3 GetCurrentUser
+=head3 GetCurrentUser Message => C<message>, Ticket => C<ticket>, Queue => C<queue>
+
+Dispatches to the C<@MailPlugins> to find one the provides
+C<GetCurrentUser> that recognizes the current user.  Mail plugins are
+tried one at a time, and stops after the first to return a current user.
+Anonymous subroutine references found in C<@MailPlugins> are treated as
+C<GetCurrentUser> methods.
+
+The default GetCurrentUser authenticator simply looks at the From:
+address, and loads or creates a user accordingly; see
+L<RT::Interface::Email::Auth::MailFrom>.
+
+Returns the current user; on failure of any plugin to do so, stops
+processing with a permanent failure and sends a generic "Permission
+Denied" mail to the user.
 
 =cut
 
@@ -323,13 +354,19 @@ sub GetCurrentUser {
     );
 }
 
-=head2 CheckACL
+=head3 CheckACL Action => C<action>, CurrentUser => C<user>, Ticket => C<ticket>, Queue => C<queue>
 
-    # Authentication Level
-    # -1 - Get out.  this user has been explicitly declined
-    # 0 - User may not do anything (Not used at the moment)
-    # 1 - Normal user
-    # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate
+Checks that the currentuser can perform a particular action.  While RT's
+standard permission controls apply, this allows a better error message,
+or more limited restrictions on the email gateway.
+
+Each plugin in C<@MailPlugins> which provides C<CheckACL> is given a
+chance to allow the action.  If any returns a true value, it
+short-circuits all later plugins.  Note that plugins may short-circuit
+and abort with failure of their own accord.
+
+Aborts processing, sending a "Permission Denied" mail to the user with
+the last plugin's failure message, on failure.
 
 =cut
 
@@ -361,6 +398,13 @@ sub CheckACL {
     );
 }
 
+=head3 HandleAction Action => C<action>, Message => C<message>, Ticket => C<ticket>, Queue => C<queue>
+
+Dispatches to the first plugin in C<@MailPlugins> which provides a
+C<HandleFoo> where C<Foo> is C<ucfirst(action)>.
+
+=cut
+
 sub HandleAction {
     my %args = (
         Action   => undef,
@@ -383,14 +427,14 @@ sub HandleAction {
 
 =head3 ParseSenderAddressFromHead HEAD
 
-Takes a MIME::Header object. Returns (user at host, friendly name, errors)
-where the first two values are the From (evaluated in order of
-Reply-To:, From:, Sender).
+Takes a L<MIME::Header> object. Returns a list of (email address,
+friendly name, errors) where the address and name are the first address
+found in C<Reply-To>, C<From>, or C<Sender>.
 
-A list of error messages may be returned even when a Sender value is
-found, since it could be a parse error for another (checked earlier)
-sender field. In this case, the errors aren't fatal, but may be useful
-to investigate the parse failure.
+A list of error messages may be returned even when an address is found,
+since it could be a parse error for another (checked earlier) sender
+field. In this case, the errors aren't fatal, but may be useful to
+investigate the parse failure.
 
 =cut
 
@@ -412,9 +456,8 @@ sub ParseSenderAddressFromHead {
 
 =head3 ParseErrorsToAddressFromHead HEAD
 
-Takes a MIME::Header object. Return a single value : user at host
-of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
-From:, Sender)
+Takes a L<MIME::Header> object. Returns the first email address found in
+C<Return-path>, C<Errors-To>, C<Reply-To>, C<From>, or C<Sender>.
 
 =cut
 
@@ -430,16 +473,9 @@ sub ParseErrorsToAddressFromHead {
     }
 }
 
-=head3 _HandleMachineGeneratedMail
+=head3 IsMachineGeneratedMail Message => C<message>
 
-Takes named params:
-    Message
-    ErrorsTo
-    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",
-"This message appears to be a loop (boolean)" );
+Checks if the mail is machine-generated (via a bounce, mail headers,
 
 =cut
 
@@ -495,9 +531,8 @@ sub IsMachineGeneratedMail {
 
 =head3 CheckForLoops HEAD
 
-Takes a HEAD object of L<MIME::Head> class and returns true if the
-message's been sent by this RT instance. Uses "X-RT-Loop-Prevention"
-field of the head for test.
+Takes a L<MIME::Head> object and returns true if the message was sent by
+this RT instance, by checking the C<X-RT-Loop-Prevention> header.
 
 =cut
 
@@ -552,16 +587,17 @@ sub CheckForAutoGenerated {
     return 0;
 }
 
-=head2 ExtractTicketId
+=head3 ExtractTicketId
 
-Passed a MIME::Entity.  Returns a ticket id or undef to signal 'new ticket'.
+Passed a L<MIME::Entity> object, and returns a either ticket id or undef
+to signal 'new ticket'.
 
 This is a great entry point if you need to customize how ticket ids are
-handled for your site. RT-Extension-RepliesToResolved demonstrates one
-possible use for this extension.
+handled for your site. L<RT::Extension::RepliesToResolved> demonstrates
+one possible use for this extension.
 
-If the Subject of this ticket is modified, it will be reloaded by the
-mail gateway code before Ticket creation.
+If the Subject of the L<MIME::Entity> is modified, the updated subject
+will be used during ticket creation.
 
 =cut
 
@@ -573,7 +609,7 @@ sub ExtractTicketId {
     return ParseTicketId( $subject );
 }
 
-=head2 ParseTicketId
+=head3 ParseTicketId
 
 Takes a string and searches for [subjecttag #id]
 
@@ -611,21 +647,35 @@ Sends an error message. Takes a param hash:
 
 =over 4
 
-=item From - sender's address, by default is 'CorrespondAddress';
+=item From
+
+Sender's address, defaults to L<RT_Config/$CorrespondAddress>;
+
+=item To
+
+Recipient, defaults to L<RT_Config/$OwnerEmail>;
+
+=item Subject
+
+Subject of the message, defaults to C<There has been an error>;
+
+=item Explanation
+
+Main content of the error, default value is C<Unexplained error>;
 
-=item To - recipient, by default is 'OwnerEmail';
+=item MIMEObj
 
-=item Subject - subject of the message, default is 'There has been an error';
+Optional L<MIME::Entity> that is attached to the error mail.
+Additionally, the C<In-Reply-To> header will point to this message.
 
-=item Explanation - main content of the error, default value is 'Unexplained error';
+=item Attach
 
-=item MIMEObj - optional MIME entity that's attached to the error mail, as well we
-add 'In-Reply-To' field to the error that points to this message.
+Optional text that attached to the error as a C<message/rfc822> part.
 
-=item Attach - optional text that attached to the error as 'message/rfc822' part.
+=item LogLevel
 
-=item LogLevel - log level under which we should write the subject and
-explanation message into the log, by default we log it as critical.
+Log level the subject and explanation is written to the log; defaults to
+C<critical>.
 
 =back
 
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
index e2e3504..c51101e 100644
--- a/lib/RT/Interface/Email/Action/Defaults.pm
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -56,6 +56,18 @@ with 'RT::Interface::Email::Role';
 
 use RT::Interface::Email;
 
+=head1 NAME
+
+RT::Interface::Email::Action::Defaults - RT's core email integration
+
+=head1 SYNOPSIS
+
+This module B<should not> be explicitly included in
+L<RT_Config/@MailPlugins>; RT includes it automatically.  It provides
+the C<comment> and C<correspond> actions.
+
+=cut
+
 sub _HandleCreate {
     my %args = (
         Subject     => undef,
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
index b0fdaf7..bc0058d 100644
--- a/lib/RT/Interface/Email/Action/Resolve.pm
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -54,6 +54,21 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Action::Resolve - Resolve tickets via the mail gateway
+
+=head1 SYNOPSIS
+
+This plugin, if placed in L<RT_Config/@MailPlugins>, allows the mail
+gateway to specify a resolve action:
+
+    | rt-mailgate --action correspond-resolve --queue General --url http://localhost/
+
+This can alternately (and more flexibly) be accomplished with a Scrip.
+
+=cut
+
 sub CheckACL {
     my %args = (
         Message     => undef,
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
index e18d439..1813a94 100644
--- a/lib/RT/Interface/Email/Action/Take.pm
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -54,6 +54,21 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Action::Take - Take tickets via the mail gateway
+
+=head1 SYNOPSIS
+
+This plugin, if placed in L<RT_Config/@MailPlugins>, allows the mail
+gateway to specify a take action:
+
+    | rt-mailgate --action take-correspond --queue General --url http://localhost/
+
+This can alternately (and more flexibly) be accomplished with a Scrip.
+
+=cut
+
 sub CheckACL {
     my %args = (
         Message     => undef,
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index f92bfe1..89033c8 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -54,6 +54,23 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Auth::MailFrom - The default mail gateway authenticator
+
+=head1 SYNOPSIS
+
+This is the default authentication plugin for RT's email gateway; no no
+other authentication plugin is found in L<RT_Config/@MailPlugins>, RT
+will default to this one.
+
+This plugin reads the first address found in the C<Reply-To>, C<From>,
+and C<Sender> headers, and loads or creates the user.  It performs no
+checking of the identity of the user, and trusts the headers of the
+incoming email.
+
+=cut
+
 sub GetCurrentUser {
     my %args = (
         Message => undef,
diff --git a/lib/RT/Interface/Email/Authz/Default.pm b/lib/RT/Interface/Email/Authz/Default.pm
index 545e23f..9a96a0a 100644
--- a/lib/RT/Interface/Email/Authz/Default.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -54,6 +54,20 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Authz::Default - RT's core authorization for the mail gateway
+
+=head1 SYNOPSIS
+
+This module B<should not> be explicitly included in
+L<RT_Config/@MailPlugins>; RT includes it automatically.  It provides
+authorization checks for the core the C<comment> and C<correspond>
+actions, via examining RT's standard rights for C<CommentOnTicket>,
+C<ReplyToTicket>, or C<CreateTicket> as necessary.
+
+=cut
+
 sub CheckACL {
     my %args = (
         Message     => undef,
diff --git a/lib/RT/Interface/Email/Authz/RequireEncrypted.pm b/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
index b96cd2c..6dc159d 100644
--- a/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
+++ b/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
@@ -54,6 +54,18 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Authz::RequireEncrypted - Require that incoming email be encrypted
+
+=head1 SYNOPSIS
+
+This plugin allows restricting incoming emails to those which were
+encrypted.  Plaintext emails are rejected, with a reply sent to the
+originator using the C<Error: unencrypted message> template.
+
+=cut
+
 sub BeforeDecode {
     my %args = (
         Action      => undef,
diff --git a/lib/RT/Interface/Email/Crypt.pm b/lib/RT/Interface/Email/Crypt.pm
index fd37e5d..86502f9 100644
--- a/lib/RT/Interface/Email/Crypt.pm
+++ b/lib/RT/Interface/Email/Crypt.pm
@@ -55,6 +55,10 @@ use warnings;
 
 RT::Interface::Email::Crypt - decrypting and verifying protected emails
 
+=head1 SEE ALSO
+
+See L<RT::Crypt>.
+
 =cut
 
 use RT::Crypt;
diff --git a/lib/RT/Interface/Email/Role.pm b/lib/RT/Interface/Email/Role.pm
index 2b0519d..f30ad97 100644
--- a/lib/RT/Interface/Email/Role.pm
+++ b/lib/RT/Interface/Email/Role.pm
@@ -55,6 +55,87 @@ use Role::Basic;
 
 use RT::Interface::Email;
 
+=head1 NAME
+
+RT::Interface::Email::Role - Role for mail plugins
+
+=head1 SYNOPSIS
+
+    package RT::Interface::Email::Action::Something;
+
+    use Role::Basic 'with';
+    with 'RT::Interface::Email::Role';
+
+    sub CheckACL { ... }
+    sub HandleSomething { ... }
+
+=head1 DESCRIPTION
+
+Provides a means to affect the handling of RT's mail gateway.  Mail
+plugins (which appear in L<RT_Config/@MailPlugins> should implement this
+role.
+
+See F<docs/extending/mail_plugins.pod> for a list of hook points which
+plugins can implement.
+
+=head1 METHODS
+
+=head2 TMPFAIL
+
+This should be called for configuration errors.  It results in a
+temporary failure code to the MTA, ensuring that the message is not
+lost, and will be retried later.
+
+This function should be passed the warning message to log with the
+temporary failure.  Does not return.
+
+
+=head2 FAILURE
+
+This should be used upon rejection of a message.  It will B<not> be
+retried by the MTA; as such, it should be used sparingly, and in
+conjunction with L</MailError> such that the sender is aware of the
+failure.
+
+The function should be passed a message to return to the mailgate.  Does
+not return.
+
+
+=head2 SUCCESS
+
+The message was successfully parsed.  The function takes an optional
+L<RT::Ticket> object to return to the mailgate. Does not return.
+
+
+=head2 MailError
+
+Sends an error concerning the email, or the processing thereof.  Takes
+the following arguments:
+
+=over
+
+=item To
+
+Only necessary in L</BeforeDecode> and L<BeforeDecrypt> hooks, where it
+defaults to L<RT_Config/OwnerEmail>; otherwise, defaults to the
+originator of the message.
+
+=item Subject
+
+Subject of the email
+
+=item Explanation
+
+The body of the email
+
+=item FAILURE
+
+If passed a true value, will call L</FAILURE> after sending the message.
+
+=back
+
+=cut
+
 sub TMPFAIL { RT::Interface::Email::TMPFAIL(@_) }
 sub FAILURE { RT::Interface::Email::FAILURE(@_) }
 sub SUCCESS { RT::Interface::Email::SUCCESS(@_) }

-----------------------------------------------------------------------


More information about the rt-commit mailing list