[Rt-commit] rt branch, 4.4/gateway-refactor, created. rt-4.2.3-70-g19fdd16

Alex Vandiver alexmv at bestpractical.com
Tue Mar 11 18:38:12 EDT 2014


The branch, 4.4/gateway-refactor has been created
        at  19fdd16e61ef405287e8e6b6567435c03e0cf8ab (commit)

- Log -----------------------------------------------------------------
commit 15a0c3bcdbd8359e64d3c76094038a0449fa307f
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 85f2aa2..5643315 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -14,6 +14,10 @@ before you upgrade and look for changes to features you currently use.
 The support for C<jsmin> (via the C<$JSMinPath> configuration) has been
 removed in favor of a built-in solution.
 
+=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 facdb38..72d2254 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -59,44 +59,10 @@ use UNIVERSAL::require;
 use Mail::Mailer ();
 use Text::ParseWords qw/shellwords/;
 
-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 b353907..5c9a9f8 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 b8de53f..1e8ab56 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 9b135a208c61eee2659b5ed490b3f6372465cdb7
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 72d2254..b3ce0c5 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -58,6 +58,7 @@ use File::Temp;
 use UNIVERSAL::require;
 use Mail::Mailer ();
 use Text::ParseWords qw/shellwords/;
+use MIME::Words ();
 
 =head1 NAME
 
@@ -65,735 +66,490 @@ use Text::ParseWords qw/shellwords/;
 
 =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 = $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 'Precedence' and 'X-FC-Machinegenerated'
-fields of the head in tests.
 
 =cut
 
-sub CheckForAutoGenerated {
-    my $head = shift;
+sub Gateway {
+    my $argsref = shift;
+    my %args    = (
+        action  => 'correspond',
+        queue   => '1',
+        ticket  => undef,
+        message => undef,
+        %$argsref
+    );
 
-    my $Precedence = $head->get("Precedence") || "";
-    if ( $Precedence =~ /^(bulk|junk)/i ) {
-        return (1);
-    }
+    my $SystemTicket;
+    my $Right;
 
-    # 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);
+    # Validate the action
+    my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
+    unless ($status) {
+        return (
+            -75,
+            "Invalid 'action' parameter "
+                . $actions[0]
+                . " for queue "
+                . $args{'queue'},
+            undef
+        );
     }
 
-    # First Class mailer uses this as a clue.
-    my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
-    if ( $FCJunk =~ /^true/i ) {
-        return (1);
+    my $parser = RT::EmailParser->new();
+    $parser->SmartParseMIMEEntityFromScalar(
+        Message => $args{'message'},
+        Decode => 0,
+        Exact => 1,
+    );
+
+    my $Message = $parser->Entity();
+    unless ($Message) {
+        MailError(
+            Subject     => "RT Bounce: Unparseable message",
+            Explanation => "RT couldn't process the message below",
+            Attach      => $args{'message'}
+        );
+
+        return ( 0,
+            "Failed to parse this message. Something is likely badly wrong with the message"
+        );
     }
 
-    return (0);
-}
+    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'} );
 
-sub CheckForBounce {
-    my $head = shift;
+    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,
+        );
 
-    my $ReturnPath = $head->get("Return-path") || "";
-    return ( $ReturnPath =~ /<>/ );
-}
+        $skip_plugin{ $class }++;
 
+        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;
 
-=head2 MailError PARAM HASH
+        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;
 
-Sends an error message. Takes a param hash:
+    my $head = $Message->head;
+    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
+    my $Sender = (ParseSenderAddressFromHead( $head ))[0];
+    my $From = $head->get("From");
+    chomp $From if defined $From;
 
-=over 4
+    my $MessageId = $head->get('Message-ID')
+        || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
 
-=item From - sender's address, by default is 'CorrespondAddress';
+    #Pull apart the subject line
+    my $Subject = $head->get('Subject') || '';
+    chomp $Subject;
+    
+    # Lets check for mail loops of various sorts.
+    my ($should_store_machine_generated_message, $IsALoop, $result);
+    ( $should_store_machine_generated_message, $ErrorsTo, $result, $IsALoop ) =
+      _HandleMachineGeneratedMail(
+        Message  => $Message,
+        ErrorsTo => $ErrorsTo,
+        Subject  => $Subject,
+        MessageId => $MessageId
+    );
 
-=item To - recipient, by default is 'OwnerEmail';
+    # 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 Bcc - optional Bcc recipients;
+    $args{'ticket'} ||= ExtractTicketId( $Message );
 
-=item Subject - subject of the message, default is 'There has been an error';
+    # ExtractTicketId may have been overridden, and edited the Subject
+    my $NewSubject = $Message->head->get('Subject');
+    chomp $NewSubject;
 
-=item Explanation - main content of the error, default value is 'Unexplained error';
+    $SystemTicket = RT::Ticket->new( RT->SystemUser );
+    $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
+    if ( $SystemTicket->id ) {
+        $Right = 'ReplyToTicket';
+    } else {
+        $Right = 'CreateTicket';
+    }
 
-=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.
+    # 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 );
+    }
 
-=item Attach - optional text that attached to the error as 'message/rfc822' part.
+    my ($AuthStat, $CurrentUser, $error) = GetAuthenticationLevel(
+        MailPlugins   => \@mail_plugins,
+        Actions       => \@actions,
+        Message       => $Message,
+        RawMessageRef => \$args{message},
+        SystemTicket  => $SystemTicket,
+        SystemQueue   => $SystemQueueObj,
+    );
 
-=item LogLevel - log level under which we should write the subject and
-explanation message into the log, by default we log it as critical.
+    # If authentication fails and no new user was created, get out.
+    if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
 
-=back
+        # If the plugins refused to create one, they lose.
+        unless ( $AuthStat == -1 ) {
+            _NoAuthorizedUserFound(
+                Right     => $Right,
+                Message   => $Message,
+                Requestor => $ErrorsTo,
+                Queue     => $args{'queue'}
+            );
 
-=cut
+        }
+        return ( 0, "Could not load a valid user", 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',
-        @_
-    );
-
-    $RT::Logger->log(
-        level   => $args{'LogLevel'},
-        message => "$args{Subject}: $args{'Explanation'}",
-    ) if $args{'LogLevel'};
+    # 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
+        );
+    }
 
-    # the colons are necessary to make ->build include non-standard headers
-    my %entity_args = (
-        Type                    => "multipart/mixed",
-        From                    => $args{'From'},
-        Bcc                     => $args{'Bcc'},
-        To                      => $args{'To'},
-        Subject                 => $args{'Subject'},
-        'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
-    );
 
-    # only set precedence if the sysadmin wants us to
-    if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) {
-        $entity_args{'Precedence:'} = RT->Config->Get('DefaultErrorMailPrecedence');
+    unless ($should_store_machine_generated_message) {
+        return ( 0, $result, undef );
     }
 
-    my $entity = MIME::Entity->build(%entity_args);
-    SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} );
-
-    $entity->attach( Data => $args{'Explanation'} . "\n" );
+    # if plugin's updated SystemTicket then update arguments
+    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
 
-    if ( $args{'MIMEObj'} ) {
-        $args{'MIMEObj'}->sync_headers;
-        $entity->add_part( $args{'MIMEObj'} );
-    }
+    my $Ticket = RT::Ticket->new($CurrentUser);
 
-    if ( $args{'Attach'} ) {
-        $entity->attach( Data => $args{'Attach'}, Type => 'message/rfc822' );
+    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
+    {
 
-    }
+        my @Cc;
+        my @Requestors = ( $CurrentUser->id );
 
-    SendEmail( Entity => $entity, Bounce => 1 );
-}
+        if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
+            @Cc = ParseCcAddressesFromHead(
+                Head        => $head,
+                CurrentUser => $CurrentUser,
+                QueueObj    => $SystemQueueObj
+            );
+        }
 
+        $head->replace('X-RT-Interface' => 'Email');
 
-=head2 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ]
+        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 );
+        }
 
-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.
+        # 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;
 
-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.
+    } elsif ( $args{'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.
+        $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
+            );
 
-Returns 1 on success, 0 on error or -1 if message has no recipients
-and hasn't been sent.
+            return ( 0, $error );
+        }
+        $args{'ticket'} = $Ticket->id;
+    } else {
+        return ( 1, "Success", $Ticket );
+    }
 
-=head3 Signing and Encrypting
+    # }}}
 
-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.
+    my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
+    foreach my $action (@actions) {
 
-The following precedence of arguments are used to figure out if
-the message should be encrypted and/or signed:
+        #   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) {
 
-* if Sign or Encrypt argument is defined then its value is 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 );
+}
 
-* else if Transaction's first attachment has X-RT-Sign or X-RT-Encrypt
-header field then it's value is used
+=head2 IsCorrectAction
 
-* else properties of a queue of the Ticket are used.
+Returns a list of valid actions we've found for this message
 
 =cut
 
-sub WillSignEncrypt {
-    my %args = @_;
-    my $attachment = delete $args{Attachment};
-    my $ticket     = delete $args{Ticket};
-
-    if ( not RT->Config->Get('Crypt')->{'Enable'} ) {
-        $args{Sign} = $args{Encrypt} = 0;
-        return wantarray ? %args : 0;
+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 = $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->set( 'Precedence', $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->set( 'Date', $date->RFC2822( Timezone => 'server' ) );
-    }
-    unless ( $head->get('MIME-Version') ) {
-        # We should never have to set the MIME-Version header
-        $head->set( 'MIME-Version', '1.0' );
-    }
-    unless ( $head->get('Content-Transfer-Encoding') ) {
-        # fsck.com #5959: Since RT sends 8bit mail, we should say so.
-        $head->set( '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 $QueueName = $TicketObj->QueueObj->Name;
-                my $QueueAddressOverride = $Overrides->{$QueueName};
-
-                if ($QueueAddressOverride) {
-                    $OutgoingMailAddress = $QueueAddressOverride;
-                } else {
-                    $OutgoingMailAddress ||= $TicketObj->QueueObj->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;
-        }
-    }
-    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->set( $_ => Encode::encode_utf8( $args{ $_ } ) )
-        foreach grep defined $args{$_}, qw(To Cc Bcc From);
-
-    $mail->head->set( $_ => $args{ExtraHeaders}{$_} )
-        foreach keys %{ $args{ExtraHeaders} };
-
-    SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
-
-    return SendEmail( Entity => $mail );
-}
-
-=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 {
-        my $txns = $ticket->Transactions;
-        $txns->Limit(
-            FIELD => 'Type',
-            VALUE => $_,
-        ) for qw(Create Correspond);
-
-        while ( my $txn = $txns->Next ) {
-            $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
+            no strict 'refs';
+            $Code = *{ $_ . "::GetCurrentUser" }{CODE};
         }
-    }
-    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 = $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 ) {
 
-        # gives an error...
-        $RT::Logger->crit("Can't encode! Charset or encoding too big.");
-        return ($value);
-    }
+    # 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}]}).
 
-    return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
+You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
+queue @{[$args{'Queue'}]}.
 
-    $value =~ s/\s+$//;
+EOT
+        MIMEObj  => $args{'Message'},
+        LogLevel => 'error'
+    );
 
-    # we need perl string to split thing char by char
-    Encode::_utf8_on($value) unless Encode::is_utf8($value);
+    # 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 {
@@ -854,7 +610,6 @@ sub CreateUser {
 }
 
 
-
 =head2 ParseCcAddressesFromHead HASH
 
 Takes a hash containing QueueObj, Head and CurrentUser objects.
@@ -924,124 +679,225 @@ From:, Sender)
 
 =cut
 
-sub ParseErrorsToAddressFromHead {
+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 = $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',    $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;
 
-    #Figure out who's sending this message.
+    # If this instance of RT sent it our, we don't want to take it in
+    my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
+    chomp ($RTLoop); # remove that newline
+    if ( $RTLoop eq RT->Config->Get('rtname') ) {
+        return 1;
+    }
 
-    foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
+    # 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;
+}
 
-        # If there's a header of that name
-        my $headerobj = $head->get($header);
-        if ($headerobj) {
-            my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
+=head2 CheckForSuspiciousSender HEAD
 
-            # If it's got actual useful content...
-            return ($addr) if ($addr);
-        }
-    }
-}
+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;
 
-=head2 ParseAddressFromHeader ADDRESS
+    #if it's from a postmaster or mailer daemon, it's likely a bounce.
 
-Takes an address from C<$head->get('Line')> and returns a tuple: user at host, friendly name
+    #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.
 
-=cut
+    #TODO: search through the whole email and find the right Ticket ID.
 
-sub ParseAddressFromHeader {
-    my $Addr = shift;
+    my ( $From, $junk ) = ParseSenderAddressFromHead($head);
 
-    # Some broken mailers send:  ""Vincent, Jesse"" <jesse at fsck.com>. Hate
-    $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;
-    my @Addresses = RT::EmailParser->ParseEmailAddress($Addr);
+    # 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);
 
-    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 'Precedence' and '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->set( $field =>
-            join ', ', map $_->format, grep !$skip{ lc $_->address },
-                Email::Address->parse( $head->get( $field ) )
-        );
+    my $Precedence = $head->get("Precedence") || "";
+    if ( $Precedence =~ /^(bulk|junk)/i ) {
+        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 = $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->set( 'In-Reply-To' => Encode::encode_utf8(join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
-    $mail->head->set( 'References' => Encode::encode_utf8(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
@@ -1097,642 +953,780 @@ 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 = $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" );
+}
+
+=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                    => $args{'From'},
+        Bcc                     => $args{'Bcc'},
+        To                      => $args{'To'},
+        Subject                 => $args{'Subject'},
+        'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
+    );
 
-    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/;
+    # only set precedence if the sysadmin wants us to
+    if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) {
+        $entity_args{'Precedence:'} = 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( Data => $args{'Explanation'} . "\n" );
 
-=head2 Gateway ARGSREF
+    if ( $args{'MIMEObj'} ) {
+        $args{'MIMEObj'}->sync_headers;
+        $entity->add_part( $args{'MIMEObj'} );
+    }
 
+    if ( $args{'Attach'} ) {
+        $entity->attach( Data => $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 = $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->set( 'Precedence', $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->set( 'Date', $date->RFC2822( Timezone => 'server' ) );
+    }
+    unless ( $head->get('MIME-Version') ) {
+        # We should never have to set the MIME-Version header
+        $head->set( 'MIME-Version', '1.0' );
+    }
+    unless ( $head->get('Content-Transfer-Encoding') ) {
+        # fsck.com #5959: Since RT sends 8bit mail, we should say so.
+        $head->set( '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 $QueueName = $TicketObj->QueueObj->Name;
+                my $QueueAddressOverride = $Overrides->{$QueueName};
+
+                if ($QueueAddressOverride) {
+                    $OutgoingMailAddress = $QueueAddressOverride;
+                } else {
+                    $OutgoingMailAddress ||= $TicketObj->QueueObj->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';
 
-        $skip_plugin{ $class }++;
+            # if something wrong with $mail->print we will get PIPE signal, handle it
+            local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
 
-        my $Code = do {
-            no strict 'refs';
-            *{ $class . "::GetCurrentUser" }{CODE};
+            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 = $head->get("From");
-    chomp $From if defined $From;
+=head2 PrepareEmailUsingTemplate Template => '', Arguments => {}
 
-    my $MessageId = $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 = $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 = $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->set( $_ => Encode::encode_utf8( $args{ $_ } ) )
+        foreach grep defined $args{$_}, qw(To Cc Bcc From);
 
+    $mail->head->set( $_ => $args{ExtraHeaders}{$_} )
+        foreach keys %{ $args{ExtraHeaders} };
 
-    unless ($should_store_machine_generated_message) {
-        return ( 0, $result, undef );
-    }
+    SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
 
-    # if plugin's updated SystemTicket then update arguments
-    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
+    return SendEmail( Entity => $mail );
+}
 
-    my $Ticket = RT::Ticket->new($CurrentUser);
+=head2 GetForwardFrom Ticket => undef, Transaction => undef
 
-    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
-    {
+Resolve the From field to use in forward mail
 
-        my @Cc;
-        my @Requestors = ( $CurrentUser->id );
+=cut
 
-        if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
-            @Cc = ParseCcAddressesFromHead(
-                Head        => $head,
-                CurrentUser => $CurrentUser,
-                QueueObj    => $SystemQueueObj
-            );
-        }
+sub GetForwardFrom {
+    my %args   = ( Ticket => undef, Transaction => undef, @_ );
+    my $txn    = $args{Transaction};
+    my $ticket = $args{Ticket} || $txn->Object;
 
-        $head->replace('X-RT-Interface' => 'Email');
+    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
+
+=cut
 
-    } elsif ( $args{'ticket'} ) {
+sub GetForwardAttachments {
+    my %args   = ( Ticket => undef, Transaction => undef, @_ );
+    my $txn    = $args{Transaction};
+    my $ticket = $args{Ticket} || $txn->Object;
 
-        $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
-            );
+    my $attachments = RT::Attachments->new( $ticket->CurrentUser );
+    if ($txn) {
+        $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
+    }
+    else {
+        my $txns = $ticket->Transactions;
+        $txns->Limit(
+            FIELD => 'Type',
+            VALUE => $_,
+        ) for qw(Create Correspond);
 
-            return ( 0, $error );
+        while ( my $txn = $txns->Next ) {
+            $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
         }
-        $args{'ticket'} = $Ticket->id;
-    } else {
-        return ( 1, "Success", $Ticket );
     }
+    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 = $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 = $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" );
+
+    # redo without broken recipients
+    %res = RT::Crypt->SignEncrypt( %args );
+    return 0 if $res{'exit_code'};
+
+    return 1;
 }
 
-=head2 _NoAuthorizedUserFound
+=head2 DeleteRecipientsFromHead HEAD RECIPIENTS
 
-Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
+Gets a head object and list of addresses.
+Deletes addresses from To, Cc or Bcc fields.
 
 =cut
 
-sub _NoAuthorizedUserFound {
-    my %args = (
-        Right     => undef,
-        Message   => undef,
-        Requestor => undef,
-        Queue     => undef,
-        @_
-    );
+sub DeleteRecipientsFromHead {
+    my $head = shift;
+    my %skip = map { lc $_ => 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}]}).
+    foreach my $field ( qw(To Cc Bcc) ) {
+        $head->set( $field =>
+            join ', ', map $_->format, grep !$skip{ lc $_->address },
+                Email::Address->parse( $head->get( $field ) )
+        );
+    }
+}
 
-You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
-queue @{[$args{'Queue'}]}.
+=head2 EncodeToMIME
 
-EOT
-        MIMEObj  => $args{'Message'},
-        LogLevel => 'error'
-    );
+Takes a hash with a String and a Charset. Returns the string encoded
+according to RFC2047, using B (base64 based) encoding.
 
-    # 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.
+String must be a perl string, octets are returned.
 
-EOT
-        MIMEObj  => $args{'Message'},
-        LogLevel => 'error'
+If Charset is not provided then $EmailOutputEncoding config option
+is used, or "latin-1" if that is not set.
+
+=cut
+
+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';
 
-=head2 _HandleMachineGeneratedMail
+    # using RFC2047 notation, sec 2.
+    # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
 
-Takes named params:
-    Message
-    ErrorsTo
-    Subject
+    # 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;
 
-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)" );
+    chomp $value;
 
-=cut
+    if ( $max <= 0 ) {
 
-sub _HandleMachineGeneratedMail {
-    my %args = ( Message => undef, ErrorsTo => undef, Subject => undef, MessageId => undef, @_ );
-    my $head = $args{'Message'}->head;
-    my $ErrorsTo = $args{'ErrorsTo'};
+        # gives an error...
+        $RT::Logger->crit("Can't encode! Charset or encoding too big.");
+        return ($value);
+    }
 
-    my $IsBounce = CheckForBounce($head);
+    return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
 
-    my $IsAutoGenerated = CheckForAutoGenerated($head);
+    $value =~ s/\s+$//;
 
-    my $IsSuspiciousSender = CheckForSuspiciousSender($head);
+    # we need perl string to split thing char by char
+    Encode::_utf8_on($value) unless Encode::is_utf8($value);
 
-    my $IsALoop = CheckForLoops($head);
+    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;
 
-    my $SquelchReplies = 0;
+    # encode an join chuncks
+    $value = join "\n ",
+        map MIME::Words::encode_mimeword( $_, $encoding, $charset ),
+        @chunks;
+    return ($value);
+}
 
-    my $owner_mail = RT->Config->Get('OwnerEmail');
+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;
 
-    #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 "<rt-". $RT::VERSION ."-". $$ ."-". CORE::time() ."-". int(rand(2000)) .'.'
+        . $ticket_id ."-". $scrip_id ."-". $sent ."@". $org .">" ;
+}
 
-    # 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.");
+sub SetInReplyTo {
+    my %args = (
+        Message   => undef,
+        InReplyTo => undef,
+        Ticket    => undef,
+        @_
+    );
+    return unless $args{'Message'} && $args{'InReplyTo'};
 
-        #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}
-            );
+    my $get_header = sub {
+        my @res;
+        if ( $args{'InReplyTo'}->isa('MIME::Entity') ) {
+            @res = $args{'InReplyTo'}->head->get( shift );
+        } else {
+            @res = $args{'InReplyTo'}->GetHeader( shift ) || '';
         }
+        return grep length, map { split /\s+/m, $_ } grep defined, @res;
+    };
 
-        #Do we actually want to store it?
-        return ( 0, $ErrorsTo, "Message Bounced", $IsALoop )
-            unless RT->Config->Get('StoreLoops');
+    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');
     }
-
-    # 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');
+    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;
 
-    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',    $Sender );
-        $head->replace( 'RT-DetectedAutoGenerated', 'true' );
-    }
-    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
+    my $mail = $args{'Message'};
+    $mail->head->set( 'In-Reply-To' => Encode::encode_utf8(join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
+    $mail->head->set( 'References' => Encode::encode_utf8(join ' ', @references) );
 }
 
-=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 670a057e818af5d22e0d80331a8bd23f9245410c
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 b3ce0c5..9562324 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -66,7 +66,9 @@ use MIME::Words ();
 
 =head1 METHODS
 
-=head2 Gateway ARGSREF
+=head2 RECEIVING MAIL
+
+=head3 Gateway ARGSREF
 
 Takes parameters:
 
@@ -389,7 +391,7 @@ sub Gateway {
     return ( 1, "Success", $Ticket );
 }
 
-=head2 IsCorrectAction
+=head3 IsCorrectAction
 
 Returns a list of valid actions we've found for this message
 
@@ -432,7 +434,7 @@ sub _LoadPlugins {
     return @res;
 }
 
-=head2 GetAuthenticationLevel
+=head3 GetAuthenticationLevel
 
     # Authentication Level
     # -1 - Get out.  this user has been explicitly declined
@@ -505,7 +507,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"
 
@@ -610,7 +612,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
@@ -639,7 +641,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
@@ -671,7 +673,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:,
@@ -699,7 +701,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
 
@@ -720,7 +722,7 @@ sub ParseAddressFromHeader {
     return ( $AddrObj->address, $AddrObj->phrase );
 }
 
-=head2 _HandleMachineGeneratedMail
+=head3 _HandleMachineGeneratedMail
 
 Takes named params:
     Message
@@ -800,7 +802,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"
@@ -823,7 +825,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.
@@ -861,7 +863,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 'Precedence' and 'X-FC-Machinegenerated'
@@ -998,7 +1000,7 @@ sub _RunUnsafeAction {
     return ( 1, "Success" );
 }
 
-=head2 MailError PARAM HASH
+=head3 MailError PARAM HASH
 
 Sends an error message. Takes a param hash:
 
@@ -1077,8 +1079,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
@@ -1295,7 +1298,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).
@@ -1325,7 +1328,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.
 
@@ -1364,7 +1367,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
 
@@ -1384,7 +1387,7 @@ sub GetForwardFrom {
     }
 }
 
-=head2 GetForwardAttachments Ticket => undef, Transaction => undef
+=head3 GetForwardAttachments Ticket => undef, Transaction => undef
 
 Resolve the Attachments to forward
 
@@ -1442,7 +1445,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.
@@ -1548,7 +1551,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.
@@ -1567,7 +1570,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.
@@ -1744,7 +1747,7 @@ sub _RecordSendEmailFailure {
     }
 }
 
-=head2 ConvertHTMLToText HTML
+=head3 ConvertHTMLToText HTML
 
 Takes HTML and converts it to plain text.  Appropriate for generating a
 plain text part from an HTML part of an email.  Returns undef if

commit 5dfc0092ff8b390debd87971d01334321529bcc5
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 7589701..d2a0c07 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.
@@ -432,9 +433,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 b9ed3c283f0a2602d8bdb224cd06e384a58e1a52
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 d2a0c07..98f2b56 100644
--- a/bin/rt-mailgate.in
+++ b/bin/rt-mailgate.in
@@ -419,73 +419,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 e3d18e37761f2116b6a87b83bccc1013405ff9ec
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 283cc45..b6d671f 100644
--- a/lib/RT/EmailParser.pm
+++ b/lib/RT/EmailParser.pm
@@ -280,41 +280,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( $self->Head->get('To') );
-    my @CcObjs = Email::Address->parse( $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 a93449cb57d7a86f3eea5d45d3f76dc5c15dc1c6
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 9562324..b5648aa 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -59,6 +59,7 @@ use UNIVERSAL::require;
 use Mail::Mailer ();
 use Text::ParseWords qw/shellwords/;
 use MIME::Words ();
+use Scope::Upper qw/unwind HERE/;
 
 =head1 NAME
 
@@ -99,9 +100,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;
@@ -113,21 +118,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(
@@ -144,7 +148,7 @@ sub Gateway {
             Attach      => $args{'message'}
         );
 
-        return ( 0,
+        FAILURE(
             "Failed to parse this message. Something is likely badly wrong with the message"
         );
     }
@@ -186,11 +190,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;
@@ -222,9 +223,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 );
@@ -242,9 +242,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,
@@ -266,9 +265,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
@@ -280,20 +278,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;
 
     # if plugin's updated SystemTicket then update arguments
     $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
@@ -330,7 +324,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
@@ -350,11 +344,11 @@ sub Gateway {
                 MIMEObj     => $Message
             );
 
-            return ( 0, $error );
+            FAILURE( $error );
         }
         $args{'ticket'} = $Ticket->id;
     } else {
-        return ( 1, "Success", $Ticket );
+        SUCCESS( $Ticket );
     }
 
     # }}}
@@ -375,20 +369,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
@@ -976,7 +969,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;
@@ -991,13 +984,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 f5938fb..51f3b71 100644
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -235,6 +235,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 f993bae6d182ca3380853e0d6f48dbf7617cf234
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 b5648aa..59c5611 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -188,10 +188,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 ac12a05..39532d0 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 5c9a9f8..b210b29 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 64fdc2347a04a0dbfb6d925adf018de633936a74
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 59c5611..d68835d 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -410,9 +410,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 is not an RT::Interface::Email::Role");
                 next;
             }
             push @res, $Class;

commit 1a448ccc99c8f6cfe33cbdd50d4f3546182f79d2
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 d68835d..7ecf3dc 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -182,7 +182,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 39532d0..79863eb 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' => $_ )
         foreach grep !$seen{$_}++, @found;
-
-    return 1;
 }
 
 sub HandleErrors {

commit de17bee66ead01bff45858b0ccf265a6ea82a246
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 7ecf3dc..cbb2a76 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -208,21 +208,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
@@ -282,9 +275,6 @@ sub Gateway {
         );
     }
 
-    FAILURE( $result )
-        unless $should_store_machine_generated_message;
-
     # if plugin's updated SystemTicket then update arguments
     $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
 
@@ -762,8 +752,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
@@ -787,7 +776,7 @@ sub _HandleMachineGeneratedMail {
         $head->replace( 'RT-Squelch-Replies-To',    $Sender );
         $head->replace( 'RT-DetectedAutoGenerated', 'true' );
     }
-    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
+    return ( $ErrorsTo, $IsALoop );
 }
 
 =head3 CheckForLoops HEAD

commit 72bbb64949cc982991330f98c581c9cf3a7d7024
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 cbb2a76..e2b5ca7 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -208,8 +208,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,
@@ -776,7 +775,7 @@ sub _HandleMachineGeneratedMail {
         $head->replace( 'RT-Squelch-Replies-To',    $Sender );
         $head->replace( 'RT-DetectedAutoGenerated', 'true' );
     }
-    return ( $ErrorsTo, $IsALoop );
+    return ( $ErrorsTo );
 }
 
 =head3 CheckForLoops HEAD

commit 4d6b1f92b3c34ae754b71582e53684573463cfea
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 e2b5ca7..2dd4602 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -756,13 +756,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 e16c9c5474ce169291426640099d99795780b3ff
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 2dd4602..2fb3b98 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -195,7 +195,6 @@ sub Gateway {
     $parser->_PostProcessNewEntity;
 
     my $head = $Message->head;
-    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
     my $Sender = (ParseSenderAddressFromHead( $head ))[0];
     my $From = $head->get("From");
     chomp $From if defined $From;
@@ -206,14 +205,16 @@ sub Gateway {
     #Pull apart the subject line
     my $Subject = $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 );
 
@@ -712,29 +713,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) {
@@ -754,22 +754,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',    $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',    $Sender );
-        $head->replace( 'RT-DetectedAutoGenerated', 'true' );
-    }
-    return ( $ErrorsTo );
+    return 1;
 }
 
 =head3 CheckForLoops HEAD

commit b1311e026e4009b440d9b28dbccb5403935fe477
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 2fb3b98..d8c89cf 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -161,27 +161,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'},
@@ -189,7 +172,7 @@ sub Gateway {
             Actions       => \@actions,
         );
     }
-    @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
+
     $parser->_DecodeBodies;
     $parser->RescueOutlook;
     $parser->_PostProcessNewEntity;
@@ -448,8 +431,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 79863eb..4cb4772 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 44b1d1c84a5a78ad1bd4742f544aceedd187c87c
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 d8c89cf..e6081ae 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -227,17 +227,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");
     }
 
@@ -449,9 +447,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;
         }
 
@@ -459,8 +456,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 b210b29..3f53f67 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 5c2a914129d733be4af6a2033dd97d837aaf9f2e
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 e6081ae..1462b12 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -414,7 +414,7 @@ sub GetAuthenticationLevel {
         @_,
     );
 
-    my ( $CurrentUser, $AuthStat, $error );
+    my ( $CurrentUser, $AuthStat );
 
     # Initalize AuthStat so comparisons work correctly
     $AuthStat = -9999999;
@@ -460,7 +460,7 @@ sub GetAuthenticationLevel {
 
     return $AuthStat if !wantarray;
 
-    return ($AuthStat, $CurrentUser, $error);
+    return ($AuthStat, $CurrentUser);
 }
 
 =head3 _NoAuthorizedUserFound

commit e7b4f2b46775bee3d8488242b774f481de89a810
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 1462b12..0324f2f 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -122,7 +122,6 @@ sub Gateway {
     $SCOPE = HERE;
 
     my $SystemTicket;
-    my $Right;
 
     # Validate the action
     my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
@@ -207,6 +206,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 cc599838e59cdf5b962c50cf066d18ce9b6e5e6b
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 0324f2f..d28ddef 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -121,8 +121,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(
@@ -204,7 +202,7 @@ sub Gateway {
     my $NewSubject = $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 4219e9ccb9b8b1784470fa4d31c22af894e9c54f
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 d28ddef..a370449 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -198,10 +198,6 @@ sub Gateway {
 
     $args{'ticket'} ||= ExtractTicketId( $Message );
 
-    # ExtractTicketId may have been overridden, and edited the Subject
-    my $NewSubject = $Message->head->get('Subject');
-    chomp $NewSubject;
-
     my $SystemTicket = RT::Ticket->new( RT->SystemUser );
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
     my $Right;
@@ -275,6 +271,10 @@ sub Gateway {
 
         $head->replace('X-RT-Interface' => 'Email');
 
+        # ExtractTicketId may have been overridden, and edited the Subject
+        my $NewSubject = $head->get('Subject');
+        chomp $NewSubject;
+
         my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
             Queue     => $SystemQueueObj->Id,
             Subject   => $NewSubject,

commit 23852d81018d4ea2dbe9ae93012fea2a698e2de1
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 5351dba..c782f21 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -465,8 +465,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 a370449..b405660 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -508,64 +508,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 3f53f67..37a2fc0 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 c07cfc8..0d84b44 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -519,45 +519,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 289c7897d03e838b534c4c8e2eef541c89b32ec4
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/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index b405660..ce77091 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -242,10 +242,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 37a2fc0..567b985 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 785b8e7..0cd3f09 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;
 }
 
 
@@ -829,3 +822,5 @@ $m->no_warnings_ok;
 
 };
 
+undef $m;
+done_testing;

commit c4b59b4e087885c3ec8c71980cf25c4b9a8c6236
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 5643315..f3878d4 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -18,6 +18,12 @@ removed in favor of a built-in solution.
 
 RT::Interface::Email no longer exports functions.
 
+=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 ce77091..ca9d007 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -211,27 +211,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 ) {
@@ -389,7 +385,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
@@ -399,45 +441,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
@@ -456,7 +491,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 567b985..6da6024 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 0cd3f09..d6427e0 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -732,8 +732,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 60c602131d54b12fc7f58cb63f84f87d5cc959f9
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 ca9d007..dd73fb1 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -494,53 +494,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 f5329f6e390081995f60619c3798e9282d3df710
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 dd73fb1..6983402 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -200,12 +200,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 e8c4c03189ec8d7f34ddb4bfa9bd99a52dc4db32
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 6983402..df3a3a4 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -175,7 +175,6 @@ sub Gateway {
     $parser->_PostProcessNewEntity;
 
     my $head = $Message->head;
-    my $Sender = (ParseSenderAddressFromHead( $head ))[0];
     my $From = $head->get("From");
     chomp $From if defined $From;
 
@@ -214,30 +213,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.",
-        );
-    }
-
     # if plugin's updated SystemTicket then update arguments
     $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
 
@@ -437,8 +424,9 @@ sub GetCurrentUser {
 
 sub CheckACL {
     my %args = (
+        Action        => undef,
+        ErrorsTo      => undef,
         MailPlugins   => [],
-        Actions       => [],
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -446,46 +434,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 6da6024..489681e 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 d6427e0..4f31cdd 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;
 }
 
@@ -732,8 +732,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;
@@ -770,8 +770,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 5ac55e4f998951bd0692a45bff29997640b697fc
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 489681e..6e383cc 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 4f31cdd..7d7fa0a 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 1a6e40cfd2b8699e3d020e6179d37c43e63f6c28
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 1e8ab56..a344741 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 7d7fa0a..dea1eb6 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;
 }
 
@@ -735,7 +732,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;
@@ -754,7 +750,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;
@@ -773,7 +768,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 5aaefffe4e660cf49d5998942c3e0f3b0bfce7ef
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 4cb4772..ada9751 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 947f1d48db9c85cd8a3da8e83433189121dc0a73
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 df3a3a4..8c2d2d9 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 MIME::Words ();
 use Scope::Upper qw/unwind HERE/;
+use 5.010;
 
 =head1 NAME
 
@@ -150,18 +151,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'},
@@ -206,7 +200,6 @@ sub Gateway {
 
     my $CurrentUser = GetCurrentUser(
         ErrorsTo      => $ErrorsTo,
-        MailPlugins   => \@mail_plugins,
         Message       => $Message,
         RawMessageRef => \$args{message},
         Ticket        => $SystemTicket,
@@ -218,7 +211,6 @@ sub Gateway {
     CheckACL(
         Action        => $actions[0],
         ErrorsTo      => $ErrorsTo,
-        MailPlugins   => \@mail_plugins,
         Message       => $Message,
         CurrentUser   => $CurrentUser,
         Ticket        => $SystemTicket,
@@ -340,30 +332,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 is not an RT::Interface::Email::Role");
-                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 is not an RT::Interface::Email::Role");
+                    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
@@ -373,7 +378,6 @@ sub _LoadPlugins {
 sub GetCurrentUser {
     my %args = (
         ErrorsTo      => undef,
-        MailPlugins   => [],
         Message       => undef,
         RawMessageRef => undef,
         Ticket        => undef,
@@ -382,15 +386,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},
@@ -426,7 +422,6 @@ sub CheckACL {
     my %args = (
         Action        => undef,
         ErrorsTo      => undef,
-        MailPlugins   => [],
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -434,10 +429,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 0401e0af23c52c38de28283b6871254bc6b6c08c
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 98f2b56..794f38b 100644
--- a/bin/rt-mailgate.in
+++ b/bin/rt-mailgate.in
@@ -320,15 +320,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 f3878d4..3edee55 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -24,6 +24,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 d16d416..5293555 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -467,15 +467,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 8c2d2d9..994297b 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -122,14 +122,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(
@@ -217,121 +215,23 @@ sub Gateway {
         Queue         => $SystemQueueObj,
     );
 
-    # 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
-            );
-        }
-
-        $head->replace('X-RT-Interface' => 'Email');
-
-        # ExtractTicketId may have been overridden, and edited the Subject
-        my $NewSubject = $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,
@@ -343,6 +243,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" ) {
@@ -450,35 +351,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( $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)
@@ -779,50 +668,6 @@ sub ParseTicketId {
     return $id;
 }
 
-sub _RunUnsafeAction {
-    my %args = (
-        Action      => undef,
-        ErrorsTo    => undef,
-        Message     => undef,
-        Ticket      => undef,
-        CurrentUser => undef,
-        @_
-    );
-
-    my $From = $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..5599c53
--- /dev/null
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -0,0 +1,147 @@
+# 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( $head->get( $_ ) ) ),
+            qw(To Cc);
+    }
+
+    $head->replace('X-RT-Interface' => 'Email');
+
+    # ExtractTicketId may have been overridden, and edited the Subject
+    my $subject = $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..5af55c5
--- /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 = $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..0ab3dbd
--- /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 = $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 3562d2b..f1c7a6a 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -1249,7 +1249,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 dea1eb6..840311a 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;
@@ -603,10 +603,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);
@@ -816,7 +812,5 @@ is( $tick->Transactions->Count, 6, "transactions added" );
 
 $m->no_warnings_ok;
 
-};
-
 undef $m;
 done_testing;

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

    It is possible that plugins will alter @actions; ensure that we have a valid action before calling it

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 994297b..d0a05c1 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -363,7 +363,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 83090c2893186444457124fbdb360b351db24af2
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 d0a05c1..1833a62 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -243,6 +243,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 6e383cc..e534ac9 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 72%
copy from lib/RT/Interface/Email/Auth/MailFrom.pm
copy to lib/RT/Interface/Email/Authz/Default.pm
index 6e383cc..cdc11f0 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -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 a538af3aac19d27c51138dd0b51a4561ebe6242d
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 1833a62..b387936 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -108,7 +108,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    = (
@@ -182,11 +181,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 );
@@ -197,7 +202,6 @@ sub Gateway {
         unless $SystemTicket->id || $SystemQueueObj->id;
 
     my $CurrentUser = GetCurrentUser(
-        ErrorsTo      => $ErrorsTo,
         Message       => $Message,
         RawMessageRef => \$args{message},
         Ticket        => $SystemTicket,
@@ -208,7 +212,6 @@ sub Gateway {
     # may have gotten rights by the time they happen.
     CheckACL(
         Action        => $actions[0],
-        ErrorsTo      => $ErrorsTo,
         Message       => $Message,
         CurrentUser   => $CurrentUser,
         Ticket        => $SystemTicket,
@@ -221,7 +224,6 @@ sub Gateway {
     for my $action (@actions) {
         HandleAction(
             Action      => $action,
-            ErrorsTo    => $ErrorsTo,
             Subject     => $Subject,
             Message     => $Message,
             Ticket      => $Ticket,
@@ -279,7 +281,6 @@ sub Plugins {
 
 sub GetCurrentUser {
     my %args = (
-        ErrorsTo      => undef,
         Message       => undef,
         RawMessageRef => undef,
         Ticket        => undef,
@@ -302,10 +303,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");
 }
@@ -323,7 +322,6 @@ sub GetCurrentUser {
 sub CheckACL {
     my %args = (
         Action        => undef,
-        ErrorsTo      => undef,
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -333,7 +331,6 @@ sub CheckACL {
 
     for my $Code ( Plugins( Method => "CheckACL" ) ) {
         return if $Code->(
-            ErrorsTo      => $args{ErrorsTo},
             Message       => $args{Message},
             CurrentUser   => $args{CurrentUser},
             Action        => $args{Action},
@@ -344,10 +341,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}" );
 }
@@ -355,7 +350,6 @@ sub CheckACL {
 sub HandleAction {
     my %args = (
         Action   => undef,
-        ErrorsTo => undef,
         Subject  => undef,
         Message  => undef,
         Ticket   => undef,
@@ -500,7 +494,6 @@ sub IsMachineGeneratedMail {
                 To          => $owner_mail,
                 Subject     => "RT Bounce: ".$args{'Subject'},
                 Explanation => "RT thinks this message may be a bounce",
-                MIMEObj     => $args{Message}
             );
         }
 
@@ -683,8 +676,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';
@@ -704,7 +695,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',
@@ -723,7 +713,6 @@ sub MailError {
     my %entity_args = (
         Type                    => "multipart/mixed",
         From                    => $args{'From'},
-        Bcc                     => $args{'Bcc'},
         To                      => $args{'To'},
         Subject                 => $args{'Subject'},
         'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
index 5599c53..bb5a539 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,
@@ -97,10 +96,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} );
 }
@@ -116,7 +113,6 @@ sub HandleCorrespond {
 sub _HandleEither {
     my %args = (
         Action      => undef,
-        ErrorsTo    => undef,
         Message     => undef,
         Subject     => undef,
         Ticket      => undef,
@@ -130,10 +126,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 5af55c5..f717100 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 0ab3dbd..ef957c1 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 5165ecea102861885aafc7aebf69d3f380d3cc73
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 40dcec1..c1cef7c 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -624,22 +624,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 68b2c5ef46b93509e130da6f084f2f7b500d48bd
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 c1cef7c..5ca93d6 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -623,7 +623,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 d89cdcd..c2bf3d3 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 316d2fa..9267658 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 b387936..df80195 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -236,17 +236,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" ) {
@@ -267,6 +267,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 ada9751..1a6ccd7 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 840311a..52f12e0 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 607666ffc13bc71f8ad7f60718ed8f4432a263a6
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 5293555..e88ccdb 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2299,9 +2299,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 5ca93d6..c7eb019 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -623,15 +623,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 c2bf3d3..a483a37 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 9267658..bdcc85f 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 1a6ccd7..52ce6ab 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 9782822..fc75406 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 acdba90..fb28872 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 da92d8cfdffcb26d1e4d2c6546f1aa85f90c25ee
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 c7eb019..dac8b47 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -623,7 +623,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 df80195..44e1952 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -51,6 +51,7 @@ package RT::Interface::Email;
 use strict;
 use warnings;
 
+use RT::Interface::Email::Crypt;
 use Email::Address;
 use MIME::Entity;
 use RT::EmailParser;
@@ -152,6 +153,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 52ce6ab..8df029c 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 981c7435a71134576e4588506466783f53cff454
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 3edee55..5de8716 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -30,6 +30,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 e88ccdb..22f6c37 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2306,9 +2306,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 true if all incoming email must be
-properly encrypted.  All unencrypted emails will be rejected by RT.
-
 Set C<RejectOnMissingPrivateKey> to false if you don't want to reject
 emails encrypted for key RT doesn't have and can not decrypt.
 
@@ -2330,7 +2327,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 8df029c..0ffcc8d 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 f8d61ad..88ea54e 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, config => 'Set( %Crypt, RejectOnUnencrypted => 1 );';
+use RT::Test::SMIME tests => undef, config => 'Set( @MailPlugins, "Authz::RequireEncrypted" );';
 my $test = 'RT::Test::SMIME';
 
 use IPC::Run3 'run3';

commit 4b243e90319efdc18e04378892da0e56ce543857
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, addording 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 b6d671f..9a40f86 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 44e1952..1a06645 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -399,15 +399,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 = $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";
@@ -427,44 +424,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 = $head->get($header);
+        next unless $value;
 
-        # If there's a header of that name
-        my $headerobj = $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 0d84b44..cc091a4 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -526,7 +526,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 7903146..f33bc65 100644
--- a/t/api/emailparser.t
+++ b/t/api/emailparser.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test nodb => 1, tests => 10;
+use RT::Test nodb => 1, tests => undef;
 
 RT->Config->Set( RTAddressRegexp => qr/^rt\@example.com$/i );
 
@@ -17,20 +17,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 f0f607677a8807dd03109126165ca69580860ef5
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 1a06645..466ff6e 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -491,7 +491,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',    $Sender );
     $head->replace( 'RT-DetectedAutoGenerated', 'true' );
 
@@ -543,7 +543,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 0ffcc8d..89942d8 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 261158b0d7ab5aa51f4efd73c9d28b87cd9ac581
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 466ff6e..3767189 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -185,9 +185,6 @@ sub Gateway {
     my $From = $head->get("From");
     chomp $From if defined $From;
 
-    my $MessageId = $head->get('Message-ID')
-        || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
-
     #Pull apart the subject line
     my $Subject = $head->get('Subject') || '';
     chomp $Subject;
@@ -198,7 +195,6 @@ sub Gateway {
         if IsMachineGeneratedMail(
             Message   => $Message,
             Subject   => $Subject,
-            MessageId => $MessageId,
         );
 
     # Make all errors from here on out bounce back to $ErrorsTo
@@ -450,7 +446,6 @@ sub IsMachineGeneratedMail {
     my %args = (
         Message => undef,
         Subject => undef,
-        MessageId => undef,
         @_
     );
     my $head = $args{'Message'}->head;
@@ -471,7 +466,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 = $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 1e75588c1d0da2c9014d1c5ed4e0a4b81efbc24f
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 3767189..c4fecd9 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -450,9 +450,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');
@@ -462,7 +460,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) {
@@ -517,81 +515,42 @@ 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 'Precedence' and 'X-FC-Machinegenerated'
-fields of the head in tests.
+is autogenerated.  This includes bounces, RFC3834 C<Auto-Submitted>
+headers, as well as heuristics including C<Precedence> and
+C<X-FC-Machinegenerated> headers.
 
 =cut
 
 sub CheckForAutoGenerated {
     my $head = shift;
 
+    # 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
     my $Precedence = $head->get("Precedence") || "";
-    if ( $Precedence =~ /^(bulk|junk)/i ) {
-        return (1);
-    }
+    return 1 if $Precedence =~ /^(bulk|junk)/i;
 
     # 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);
-}
-
-sub CheckForBounce {
-    my $head = shift;
+    return 1 if $FCJunk =~ /^true/i;
 
-    my $ReturnPath = $head->get("Return-path") || "";
-    return ( $ReturnPath =~ /<>/ );
+    return 0;
 }
 
 =head2 ExtractTicketId

commit 31dfb4fd964e2b81dab0c1b316077773fdddafb8
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 c4fecd9..fb938f7 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -141,11 +141,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,
         );
     }
 
@@ -318,8 +315,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
@@ -356,8 +353,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 {
@@ -641,6 +638,7 @@ sub MailError {
         MIMEObj     => undef,
         Attach      => undef,
         LogLevel    => 'crit',
+        FAILURE     => 0,
         @_
     );
 
@@ -679,6 +677,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 bb5a539..417d54f 100644
--- a/lib/RT/Interface/Email/Action/Defaults.pm
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -98,8 +98,8 @@ sub _HandleCreate {
     MailError(
         Subject     => "Ticket creation failed: $args{Subject}",
         Explanation => $ErrStr,
+        FAILURE     => 1,
     );
-    FAILURE("Ticket creation failed: $ErrStr", $args{Ticket} );
 }
 
 sub HandleComment {
@@ -124,12 +124,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 f717100..73f1b05 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 = $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 ef957c1..fdc4bad 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 = $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 5754fddc710b427785a5741dd325cc99041914c0
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 73f1b05..7c63b55 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 fdc4bad..aa8a819 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 = $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 19fdd16e61ef405287e8e6b6567435c03e0cf8ab
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..512adee 100644
--- a/docs/extending/mail_plugins.pod
+++ b/docs/extending/mail_plugins.pod
@@ -1,67 +1,239 @@
 =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 hooks
+
+Mail plugins are expected to provide one or more of the following
+methods:
+
+=head3 BeforeDecrypt
 
-=over 4
+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 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 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
+
 
-=item CurrentUser
 
-An C<RT::CurrentUser> object
+=head3 GetCurrentUser
 
-=item AuthStat
+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:
+
+=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 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
 
-The authentication level returned from the previous plugin.
 
-=item Ticket [OPTIONAL]
+=head3 CheckACL
 
-The ticket under discussion
+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.
 
-=item Queue [OPTIONAL]
+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.
 
-If we don't already have a ticket id, we need to know which queue we're talking about
+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
 
-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.
 
+=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
+
+=head1 SEE ALSO
+
+L<RT::Interface::Email::Role>
+
+=cut
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index fb938f7..bf4c831 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -65,7 +65,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
 
@@ -75,32 +75,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
 
@@ -243,6 +252,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,
@@ -285,7 +302,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
 
@@ -319,13 +350,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
 
@@ -357,6 +394,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,
@@ -379,14 +423,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
 
@@ -408,9 +452,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
 
@@ -426,16 +469,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
 
@@ -491,9 +527,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
 
@@ -550,16 +585,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
 
@@ -571,7 +607,7 @@ sub ExtractTicketId {
     return ParseTicketId( $subject );
 }
 
-=head2 ParseTicketId
+=head3 ParseTicketId
 
 Takes a string and searches for [subjecttag #id]
 
@@ -609,21 +645,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 417d54f..afa4f7a 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 7c63b55..452a01b 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 aa8a819..f7e2191 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 e534ac9..081a570 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 89942d8..545ac43 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