[Rt-commit] rt branch, 4.4/multiple-reply-to, created. rt-4.2.11-177-gc179f58

? sunnavy sunnavy at bestpractical.com
Mon Jul 6 12:47:03 EDT 2015


The branch, 4.4/multiple-reply-to has been created
        at  c179f586d0d2c90590d4953fe307a255949fe598 (commit)

- Log -----------------------------------------------------------------
commit 2164243a3de6ec7c5231ec1620d666c997a661eb
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 bf96bec..3e015da 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -31,6 +31,10 @@ reporting.
 Custom fields with categories will be split out into hierarchical custom
 fields.
 
+=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 ef618e6..7eb1918 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -61,43 +61,10 @@ use Text::ParseWords qw/shellwords/;
 use RT::Util 'safe_run_child';
 use File::Spec;
 
-BEGIN {
-    use base 'Exporter';
-    use vars qw ( @EXPORT_OK);
-
-    # your exported package globals go here,
-    # as well as any optionally exported functions
-    @EXPORT_OK = qw(
-        &CreateUser
-        &CheckForLoops
-        &CheckForSuspiciousSender
-        &CheckForAutoGenerated
-        &CheckForBounce
-        &MailError
-        &ParseCcAddressesFromHead
-        &ParseSenderAddressFromHead
-        &ParseErrorsToAddressFromHead
-        &ParseAddressFromHeader
-        &Gateway);
-
-}
-
 =head1 NAME
 
   RT::Interface::Email - helper functions for parsing email sent to RT
 
-=head1 SYNOPSIS
-
-  use lib "!!RT_LIB_PATH!!";
-  use lib "!!RT_ETC_PATH!!";
-
-  use RT::Interface::Email  qw(Gateway CreateUser);
-
-=head1 DESCRIPTION
-
-
-
-
 =head1 METHODS
 
 =head2 CheckForLoops HEAD
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 2d760c2..d678439 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -51,7 +51,7 @@ package RT::Interface::Email::Auth::MailFrom;
 use strict;
 use warnings;
 
-use RT::Interface::Email qw(ParseSenderAddressFromHead CreateUser);
+use RT::Interface::Email;
 
 # This is what the ordinary, non-enhanced gateway does at the moment.
 
@@ -66,7 +66,7 @@ sub GetCurrentUser {
 
 
     # We don't need to do any external lookups
-    my ( $Address, $Name, @errors ) = ParseSenderAddressFromHead( $args{'Message'}->head );
+    my ( $Address, $Name, @errors ) = RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head );
     $RT::Logger->warning("Failed to parse ".join(', ', @errors))
         if @errors;
 
@@ -175,7 +175,9 @@ sub GetCurrentUser {
         }
     }
 
-    $CurrentUser = CreateUser( undef, $Address, $Name, $Address, $args{'Message'} );
+    $CurrentUser = RT::Interface::Email::CreateUser(
+        undef, $Address, $Name, $Address, $args{'Message'}
+    );
 
     return ( $CurrentUser, 1 );
 }
diff --git a/share/html/REST/1.0/NoAuth/mail-gateway b/share/html/REST/1.0/NoAuth/mail-gateway
index 0f70880..5b45a03 100644
--- a/share/html/REST/1.0/NoAuth/mail-gateway
+++ b/share/html/REST/1.0/NoAuth/mail-gateway
@@ -55,7 +55,7 @@ $ticket => undef
 </%ARGS>
 <%init>
 $m->callback( %ARGS, CallbackName => 'Pre' );
-use RT::Interface::Email ();    # It's an exporter, but we don't care
+use RT::Interface::Email;
 $r->content_type('text/plain; charset=utf-8');
 $m->error_format('text');
 my ( $status, $error, $Ticket ) = RT::Interface::Email::Gateway( \%ARGS );

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

commit 3820f12ef509b31cffe7f25147b3ef5731544c3b
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 d52e8ff..3ab4c94 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -68,7 +68,9 @@ use MIME::Words ();
 
 =head1 METHODS
 
-=head2 Gateway ARGSREF
+=head2 RECEIVING MAIL
+
+=head3 Gateway ARGSREF
 
 
 Takes parameters:
@@ -392,7 +394,7 @@ sub Gateway {
     return ( 1, "Success", $Ticket );
 }
 
-=head2 IsCorrectAction
+=head3 IsCorrectAction
 
 Returns a list of valid actions we've found for this message
 
@@ -435,7 +437,7 @@ sub _LoadPlugins {
     return @res;
 }
 
-=head2 GetAuthenticationLevel
+=head3 GetAuthenticationLevel
 
     # Authentication Level
     # -1 - Get out.  this user has been explicitly declined
@@ -509,7 +511,7 @@ sub GetAuthenticationLevel {
 }
 
 
-=head2 _NoAuthorizedUserFound
+=head3 _NoAuthorizedUserFound
 
 Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
 
@@ -614,7 +616,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
@@ -644,7 +646,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
@@ -676,7 +678,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:,
@@ -704,7 +706,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
 
@@ -725,7 +727,7 @@ sub ParseAddressFromHeader {
     return ( $AddrObj->address, $AddrObj->phrase );
 }
 
-=head2 _HandleMachineGeneratedMail
+=head3 _HandleMachineGeneratedMail
 
 Takes named params:
     Message
@@ -805,7 +807,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"
@@ -828,7 +830,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.
@@ -866,7 +868,7 @@ sub CheckForSuspiciousSender {
     return undef;
 }
 
-=head2 CheckForAutoGenerated HEAD
+=head3 CheckForAutoGenerated HEAD
 
 Takes a HEAD object of L<MIME::Head> class and returns true if message is
 autogenerated. Checks C<Precedence>, C<Auto-Submitted>, and
@@ -1004,7 +1006,7 @@ sub _RunUnsafeAction {
     return ( 1, "Success" );
 }
 
-=head2 MailError PARAM HASH
+=head3 MailError PARAM HASH
 
 Sends an error message. Takes a param hash:
 
@@ -1088,8 +1090,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
@@ -1328,7 +1331,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).
@@ -1358,7 +1361,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.
 
@@ -1397,7 +1400,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
 
@@ -1417,7 +1420,7 @@ sub GetForwardFrom {
     }
 }
 
-=head2 GetForwardAttachments Ticket => undef, Transaction => undef
+=head3 GetForwardAttachments Ticket => undef, Transaction => undef
 
 Resolve the Attachments to forward
 
@@ -1474,7 +1477,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.
@@ -1581,7 +1584,7 @@ sub SignEncrypt {
 }
 
 
-=head2 DeleteRecipientsFromHead HEAD RECIPIENTS
+=head3 DeleteRecipientsFromHead HEAD RECIPIENTS
 
 Gets a head object and list of addresses.
 Deletes addresses from To, Cc or Bcc fields.
@@ -1600,7 +1603,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.
@@ -1776,7 +1779,7 @@ sub _RecordSendEmailFailure {
     }
 }
 
-=head2 ConvertHTMLToText HTML
+=head3 ConvertHTMLToText HTML
 
 Takes HTML characters and converts it to plain text characters.
 Appropriate for generating a plain text part from an HTML part of an

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

    Update MailPlugins to only reference existant modules

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

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

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

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

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

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

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

commit 7cf851d187b52ac54a7594eb2f8fae9e82d84c0e
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 3ab4c94..b5d28c1 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -61,6 +61,7 @@ use Text::ParseWords qw/shellwords/;
 use RT::Util 'safe_run_child';
 use File::Spec;
 use MIME::Words ();
+use Scope::Upper qw/unwind HERE/;
 
 =head1 NAME
 
@@ -102,9 +103,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;
@@ -116,21 +121,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(
@@ -147,7 +151,7 @@ sub Gateway {
             Attach      => $args{'message'}
         );
 
-        return ( 0,
+        FAILURE(
             "Failed to parse this message. Something is likely badly wrong with the message"
         );
     }
@@ -189,11 +193,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;
@@ -225,9 +226,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 );
@@ -245,9 +245,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,
@@ -269,9 +268,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
@@ -283,20 +281,16 @@ sub Gateway {
                 "You do not have permission to communicate with RT",
             MIMEObj => $Message
         );
-        return (
-            0,
+        FAILURE(
             ($CurrentUser->EmailAddress || $CurrentUser->Name)
             . " ($Sender) tried to submit a message to "
                 . $args{'Queue'}
                 . " without permission.",
-            undef
         );
     }
 
-
-    unless ($should_store_machine_generated_message) {
-        return ( 0, $result, undef );
-    }
+    FAILURE( $result )
+        unless $should_store_machine_generated_message;
 
     $head->replace('X-RT-Interface' => 'Email');
 
@@ -333,7 +327,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
@@ -353,11 +347,11 @@ sub Gateway {
                 MIMEObj     => $Message
             );
 
-            return ( 0, $error );
+            FAILURE( $error );
         }
         $args{'ticket'} = $Ticket->id;
     } else {
-        return ( 1, "Success", $Ticket );
+        SUCCESS( $Ticket );
     }
 
     # }}}
@@ -378,20 +372,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
@@ -982,7 +975,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;
@@ -997,13 +990,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 021682f..7ce5d29 100644
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -250,6 +250,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 6e714908655eeb68702a0052c6aa7f63e12a548d
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 b5d28c1..e0c467c 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -191,10 +191,6 @@ sub Gateway {
             Queue         => $SystemQueueObj,
             Actions       => \@actions,
         );
-        next if $status > 0;
-
-        SUCCESS($msg) if $status == -2;
-        FAILURE($msg) if $status == -1;
     }
     @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
     $parser->_DecodeBodies;
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 8f8e636..26d2933 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -107,6 +107,9 @@ sub ApplyBeforeDecode { return 1 }
 use RT::Crypt;
 use RT::EmailParser ();
 
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
 sub GetCurrentUser {
     my %args = (
         Message       => undef,
@@ -139,7 +142,7 @@ sub GetCurrentUser {
                 Template  => 'Error: unencrypted message',
                 Arguments => { Message  => $args{'Message'} },
             );
-            return (-1, 'rejected because the message is unencrypted with RejectOnUnencrypted enabled');
+            FAILURE('rejected because the message is unencrypted with RejectOnUnencrypted enabled');
         }
         else {
             $args{'Message'}->head->replace(
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index d678439..38f0f58 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -51,6 +51,9 @@ package RT::Interface::Email::Auth::MailFrom;
 use strict;
 use warnings;
 
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
 use RT::Interface::Email;
 
 # This is what the ordinary, non-enhanced gateway does at the moment.
diff --git a/lib/RT/Interface/Email/Role.pm b/lib/RT/Interface/Email/Role.pm
new file mode 100644
index 0000000..2b0519d
--- /dev/null
+++ b/lib/RT/Interface/Email/Role.pm
@@ -0,0 +1,64 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Interface::Email::Role;
+
+use strict;
+use warnings;
+
+use Role::Basic;
+
+use RT::Interface::Email;
+
+sub TMPFAIL { RT::Interface::Email::TMPFAIL(@_) }
+sub FAILURE { RT::Interface::Email::FAILURE(@_) }
+sub SUCCESS { RT::Interface::Email::SUCCESS(@_) }
+
+sub MailError { RT::Interface::Email::MailError(@_) }
+
+1;

commit 783cfaeac092ca27c2a73648c7144aa953b99358
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 e0c467c..0386617 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -413,9 +413,8 @@ sub _LoadPlugins {
             $Class->require or
                 do { $RT::Logger->error("Couldn't load $Class: $@"); next };
 
-            no strict 'refs';
-            unless ( defined *{ $Class . "::GetCurrentUser" }{CODE} ) {
-                $RT::Logger->crit( "No GetCurrentUser code found in $Class module");
+            unless ( $Class->DOES( "RT::Interface::Email::Role" ) ) {
+                $RT::Logger->crit( "$Class does not implement RT::Interface::Email::Role.  Mail plugins from RT 4.2 and earlier are not forward-compatible with RT 4.4.");
                 next;
             }
             push @res, $Class;

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

commit 4f3ecec9f6f104e417b674f8dcf75f993bc0f929
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 17cc375..fcaaedd 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -211,21 +211,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
@@ -285,9 +278,6 @@ sub Gateway {
         );
     }
 
-    FAILURE( $result )
-        unless $should_store_machine_generated_message;
-
     $head->replace('X-RT-Interface' => 'Email');
 
     # if plugin's updated SystemTicket then update arguments
@@ -767,8 +757,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
@@ -792,7 +781,7 @@ sub _HandleMachineGeneratedMail {
         $head->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
         $head->replace( 'RT-DetectedAutoGenerated', 'true' );
     }
-    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
+    return ( $ErrorsTo, $IsALoop );
 }
 
 =head3 CheckForLoops HEAD

commit 12dd66198b20133a41e2b271af75f4c29a17ec9f
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 fcaaedd..fc0c3a3 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -211,8 +211,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,
@@ -781,7 +780,7 @@ sub _HandleMachineGeneratedMail {
         $head->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
         $head->replace( 'RT-DetectedAutoGenerated', 'true' );
     }
-    return ( $ErrorsTo, $IsALoop );
+    return ( $ErrorsTo );
 }
 
 =head3 CheckForLoops HEAD

commit 0a620d9397f81a5fa2917d11e15d59eef7d56816
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 fc0c3a3..c284f59 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -761,13 +761,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 e02dbd84a857d72b604099a8b37295995d7d05b3
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 c284f59..01965d5 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -198,7 +198,6 @@ sub Gateway {
     $parser->_PostProcessNewEntity;
 
     my $head = $Message->head;
-    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
     my $Sender = (ParseSenderAddressFromHead( $head ))[0];
     my $From = Encode::decode( "UTF-8", $head->get("From") );
     chomp $From if defined $From;
@@ -209,14 +208,16 @@ sub Gateway {
     #Pull apart the subject line
     my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || '');
     chomp $Subject;
-    
+
     # Lets check for mail loops of various sorts.
-    ( $ErrorsTo ) = _HandleMachineGeneratedMail(
-        Message  => $Message,
-        ErrorsTo => $ErrorsTo,
-        Subject  => $Subject,
-        MessageId => $MessageId
-    );
+    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
+    $ErrorsTo = RT->Config->Get('OwnerEmail')
+        if IsMachineGeneratedMail(
+            Message   => $Message,
+            ErrorsTo  => $ErrorsTo,
+            Subject   => $Subject,
+            MessageId => $MessageId,
+        );
 
     $args{'ticket'} ||= ExtractTicketId( $Message );
 
@@ -717,29 +718,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) {
@@ -759,22 +759,16 @@ sub _HandleMachineGeneratedMail {
         FAILURE( "Message is a bounce" ) unless RT->Config->Get('StoreLoops');
     }
 
-    # Squelch replies if necessary
-    # Don't let the user stuff the RT-Squelch-Replies-To header.
-    $head->delete('RT-Squelch-Replies-To');
-
-    if ($SquelchReplies) {
+    # Squelch replies to the sender, and also leave a clue to
+    # allow us to squelch ALL outbound messages. This way we
+    # can punt the logic of "what to do when we get a bounce"
+    # to the scrip. We might want to notify nobody. Or just
+    # the RT Owner. Or maybe all Privileged watchers.
+    my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
+    $head->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
+    $head->replace( 'RT-DetectedAutoGenerated', 'true' );
 
-        # Squelch replies to the sender, and also leave a clue to
-        # allow us to squelch ALL outbound messages. This way we
-        # can punt the logic of "what to do when we get a bounce"
-        # to the scrip. We might want to notify nobody. Or just
-        # the RT Owner. Or maybe all Privileged watchers.
-        my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
-        $head->replace( 'RT-Squelch-Replies-To',    Encode::encode("UTF-8", $Sender ) );
-        $head->replace( 'RT-DetectedAutoGenerated', 'true' );
-    }
-    return ( $ErrorsTo );
+    return 1;
 }
 
 =head3 CheckForLoops HEAD

commit dbe2a635f7c6fa5fb799dbe3c5572ecfffd45d9d
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 01965d5..2418f29 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -164,27 +164,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'},
@@ -192,7 +175,7 @@ sub Gateway {
             Actions       => \@actions,
         );
     }
-    @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
+
     $parser->_DecodeBodies;
     $parser->RescueOutlook;
     $parser->_PostProcessNewEntity;
@@ -451,8 +434,8 @@ sub GetAuthenticationLevel {
         if ( ref($_) eq "CODE" ) {
             $Code = $_;
         } else {
-            no strict 'refs';
-            $Code = *{ $_ . "::GetCurrentUser" }{CODE};
+            $Code = $_->can("GetCurrentUser");
+            next unless $Code;
         }
 
         foreach my $action (@{ $args{Actions} }) {
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index f6c56ea..539d31f 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -102,15 +102,13 @@ Read also: L<RT::Crypt> and L<RT::Crypt::SMIME>.
 
 =cut
 
-sub ApplyBeforeDecode { return 1 }
-
 use RT::Crypt;
 use RT::EmailParser ();
 
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
-sub GetCurrentUser {
+sub BeforeDecode {
     my %args = (
         Message       => undef,
         RawMessageRef => undef,

commit 3f2ed6f7ab85169d1df06be4bbedcb9c64d431e2
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 2418f29..673adbe 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -230,17 +230,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");
     }
 
@@ -452,9 +450,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;
         }
 
@@ -462,8 +459,6 @@ sub GetAuthenticationLevel {
         @{$args{Actions}} = grep !$skip_action{$_}, @{$args{Actions}}
             if $AuthStat == -2;
         last unless @{$args{Actions}};
-
-        last if $AuthStat == -1;
     }
 
     return $AuthStat if !wantarray;
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 38f0f58..d055486 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -75,7 +75,7 @@ sub GetCurrentUser {
 
     unless ( $Address ) {
         $RT::Logger->error("Couldn't parse or find sender's address");
-        return ( $args{'CurrentUser'}, -1 );
+        FAILURE("Couldn't parse or find sender's address");
     }
 
     my $CurrentUser = RT::CurrentUser->new;
@@ -90,14 +90,14 @@ sub GetCurrentUser {
     my $unpriv = RT->UnprivilegedUsers();
     unless ( $unpriv->Id ) {
         $RT::Logger->crit("Couldn't find the 'Unprivileged' internal group");
-        return ( $args{'CurrentUser'}, -1 );
+        FAILURE("Couldn't find the 'Unprivileged' internal group");
     }
 
     my $everyone = RT::Group->new( RT->SystemUser );
     $everyone->LoadSystemInternalGroup('Everyone');
     unless ( $everyone->Id ) {
         $RT::Logger->crit("Couldn't find the 'Everyone' internal group");
-        return ( $args{'CurrentUser'}, -1 );
+        FAILURE("Couldn't find the 'Everyone' internal group");
     }
 
     $RT::Logger->debug("Going to create user with address '$Address'" );

commit 2d5fa31c4a52fc071f8e9f86ddec8392bc3520f9
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 673adbe..a1e035d 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -417,7 +417,7 @@ sub GetAuthenticationLevel {
         @_,
     );
 
-    my ( $CurrentUser, $AuthStat, $error );
+    my ( $CurrentUser, $AuthStat );
 
     # Initalize AuthStat so comparisons work correctly
     $AuthStat = -9999999;
@@ -463,7 +463,7 @@ sub GetAuthenticationLevel {
 
     return $AuthStat if !wantarray;
 
-    return ($AuthStat, $CurrentUser, $error);
+    return ($AuthStat, $CurrentUser);
 }
 
 

commit 0454c429e17c15731b93c941b1e680e078a8feb0
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 a1e035d..b53e35a 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -125,7 +125,6 @@ sub Gateway {
     $SCOPE = HERE;
 
     my $SystemTicket;
-    my $Right;
 
     # Validate the action
     my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
@@ -210,6 +209,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 d34399bca97ee30b6950e04fceb6bc15798f907d
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 b53e35a..08e1f74 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -124,8 +124,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(
@@ -207,7 +205,7 @@ sub Gateway {
     my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
     chomp $NewSubject;
 
-    $SystemTicket = RT::Ticket->new( RT->SystemUser );
+    my $SystemTicket = RT::Ticket->new( RT->SystemUser );
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
     my $Right;
     if ( $SystemTicket->id ) {

commit 771cfe1aa77cd20e6ca301e673da2bafac527afe
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 08e1f74..22c31e8 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -201,10 +201,6 @@ sub Gateway {
 
     $args{'ticket'} ||= ExtractTicketId( $Message );
 
-    # ExtractTicketId may have been overridden, and edited the Subject
-    my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
-    chomp $NewSubject;
-
     my $SystemTicket = RT::Ticket->new( RT->SystemUser );
     $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
     my $Right;
@@ -278,6 +274,10 @@ sub Gateway {
             );
         }
 
+        # ExtractTicketId may have been overridden, and edited the Subject
+        my $NewSubject = Encode::decode( "UTF-8", $head->get('Subject') );
+        chomp $NewSubject;
+
         my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
             Queue     => $SystemQueueObj->Id,
             Subject   => $NewSubject,

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

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

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

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

commit 9c1e5275a7c1aa0b4528022a4c48c4c4347f9d37
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 3c94271..16befc4 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -42,6 +42,11 @@ Previously, if the C<CreateTicket> right was not granted to C<Everyone>
 or C<Unprivileged>, the email would be rejected without a user; now, the
 user to be created even when the mail is rejected.
 
+=item *
+The L<RT_Config/@MailPlugins> functionality has been rewritten; mail
+plugins written for previous versions of RT will not function as
+expected.  See F<docs/extending/mail_plugins.pod>
+
 =back
 
 =cut
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 2346b1c..d245123 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -214,27 +214,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 ) {
@@ -392,7 +388,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
@@ -402,45 +444,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
@@ -459,7 +494,7 @@ sub GetAuthenticationLevel {
 
     return $AuthStat if !wantarray;
 
-    return ($AuthStat, $CurrentUser);
+    return $AuthStat;
 }
 
 
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 05027d6..3ecf9e6 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -59,14 +59,10 @@ use RT::Interface::Email;
 # This is what the ordinary, non-enhanced gateway does at the moment.
 
 sub GetCurrentUser {
-    my %args = ( Message     => undef,
-                 CurrentUser => undef,
-                 AuthLevel   => undef,
-                 Ticket      => undef,
-                 Queue       => undef,
-                 Action      => undef,
-                 @_ );
-
+    my %args = (
+        Message => undef,
+        @_,
+    );
 
     # We don't need to do any external lookups
     my ( $Address, $Name, @errors ) = RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head );
@@ -83,7 +79,7 @@ sub GetCurrentUser {
     $CurrentUser->LoadByName( $Address ) unless $CurrentUser->Id;
     if ( $CurrentUser->Id ) {
         $RT::Logger->debug("Mail from user #". $CurrentUser->Id ." ($Address)" );
-        return ( $CurrentUser, 1 );
+        return $CurrentUser;
     }
 
 
@@ -97,7 +93,22 @@ sub GetCurrentUser {
     $CurrentUser = RT::CurrentUser->new;
     $CurrentUser->Load( $user->id );
 
-    return (undef, 0) unless $CurrentUser->id;
+    return $CurrentUser;
+}
+
+
+sub CheckACL {
+    my %args = (
+        Message     => undef,
+        CurrentUser => undef,
+        AuthLevel   => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        Action      => undef,
+        @_,
+    );
+
+    my $CurrentUser = $args{CurrentUser};
 
     if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
         my $qname = $args{'Queue'}->Name;
@@ -107,7 +118,7 @@ sub GetCurrentUser {
             # check to see whether if they can comment on the ticket
             unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to comment on ticket in queue '$qname'");
-                return ( $CurrentUser, 0 );
+                return 0;
             }
         }
         elsif ( $args{'Action'} =~ /^correspond$/i ) {
@@ -115,7 +126,7 @@ sub GetCurrentUser {
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to reply to ticket in queue '$qname'");
-                return ( $CurrentUser, 0 );
+                return 0;
             }
         }
         elsif ( $args{'Action'} =~ /^take$/i ) {
@@ -123,7 +134,7 @@ sub GetCurrentUser {
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to own ticket in queue '$qname'");
-                return ( $CurrentUser, 0 );
+                return 0;
             }
 
         }
@@ -132,13 +143,13 @@ sub GetCurrentUser {
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
             unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' ) ) {
                 $RT::Logger->debug("Unprivileged users have no right to resolve ticket in queue '$qname'");
-                return ( $CurrentUser, 0 );
+                return 0;
             }
 
         }
         else {
             $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
-            return ( $CurrentUser, 0 );
+            return 0;
         }
     }
 
@@ -149,11 +160,11 @@ sub GetCurrentUser {
         # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
         unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' ) ) {
             $RT::Logger->debug("Unprivileged users have no right to create ticket in queue '$qname'");
-            return ( $CurrentUser, 0 );
+            return 0;
         }
     }
 
-    return ( $CurrentUser, 1 );
+    return 1;
 }
 
 RT::Base->_ImportOverlays();
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index 89bfcee..4e1dba8 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -729,8 +729,8 @@ ok( $status, "successfuly granted right: $msg" );
 my $ace_id = $status;
 ok( $user->HasRight( Right => 'ReplyToTicket', Object => $tick ), "User can reply to ticket" );
 
-$m->next_warning_like(qr/That user may not own tickets in that queue/);
-$m->next_warning_like(qr/Could not record email: Ticket not taken/);
+$m->next_warning_like(qr/Permission Denied: You do not have permission to communicate with RT/);
+$m->next_warning_like(qr/Could not record email: ext-mailgate@\S+ tried to submit a message to ext-mailgate without permission/);
 $m->no_leftover_warnings_ok;
 
 $! = 0;

commit 26bf806ac6a14d8362340c51cc484b02dedbcac0
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 d245123..810426b 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -498,53 +498,6 @@ sub CheckACL {
 }
 
 
-=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 5db4d3b09572cbe698559a3f40f5e83fa779ed64
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 810426b..4ee842f 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -203,12 +203,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 05271ffdb289439f776e3df74090e6b80828332f
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 4ee842f..806a51a 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -178,7 +178,6 @@ sub Gateway {
     $parser->_PostProcessNewEntity;
 
     my $head = $Message->head;
-    my $Sender = (ParseSenderAddressFromHead( $head ))[0];
     my $From = Encode::decode( "UTF-8", $head->get("From") );
     chomp $From if defined $From;
 
@@ -217,30 +216,18 @@ sub Gateway {
         Queue         => $SystemQueueObj,
     );
 
-    my $AuthStat = CheckACL(
+    # We only care about ACLs on the _first_ action, as later actions
+    # may have gotten rights by the time they happen.
+    CheckACL(
+        Action        => $actions[0],
+        ErrorsTo      => $ErrorsTo,
         MailPlugins   => \@mail_plugins,
-        Actions       => \@actions,
         Message       => $Message,
         CurrentUser   => $CurrentUser,
         Ticket        => $SystemTicket,
         Queue         => $SystemQueueObj,
     );
 
-    # If we got a user, but they don't have the right to say things
-    if ( $AuthStat == 0 ) {
-        MailError(
-            To          => $ErrorsTo,
-            Subject     => "Permission Denied",
-            Explanation =>
-                "You do not have permission to communicate with RT",
-            MIMEObj => $Message
-        );
-        FAILURE("$Sender tried to submit a message to "
-                . $args{'queue'}
-                . " without permission.",
-        );
-    }
-
     $head->replace('X-RT-Interface' => 'Email');
 
     # if plugin's updated SystemTicket then update arguments
@@ -440,8 +427,9 @@ sub GetCurrentUser {
 
 sub CheckACL {
     my %args = (
+        Action        => undef,
+        ErrorsTo      => undef,
         MailPlugins   => [],
-        Actions       => [],
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -449,46 +437,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}" );
 }
 
 
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 3ecf9e6..84992eb 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -99,72 +99,74 @@ sub GetCurrentUser {
 
 sub CheckACL {
     my %args = (
+        ErrorsTo    => undef,
         Message     => undef,
         CurrentUser => undef,
-        AuthLevel   => undef,
         Ticket      => undef,
         Queue       => undef,
         Action      => undef,
         @_,
     );
 
-    my $CurrentUser = $args{CurrentUser};
+    my $principal = $args{CurrentUser}->PrincipalObj;
+    my $email     = $args{CurrentUser}->UserObj->EmailAddress;
 
+    my $msg;
     if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
         my $qname = $args{'Queue'}->Name;
+        my $tid   = $args{'Ticket'}->id;
         # We have a ticket. that means we're commenting or corresponding
         if ( $args{'Action'} =~ /^comment$/i ) {
 
             # check to see whether if they can comment on the ticket
-            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' ) ) {
-                $RT::Logger->debug("Unprivileged users have no right to comment on ticket in queue '$qname'");
-                return 0;
-            }
+            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' );
+            $msg = "$email has no right to comment on ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^correspond$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' ) ) {
-                $RT::Logger->debug("Unprivileged users have no right to reply to ticket in queue '$qname'");
-                return 0;
-            }
+            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' );
+            $msg = "$email has no right to reply to ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^take$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' ) ) {
-                $RT::Logger->debug("Unprivileged users have no right to own ticket in queue '$qname'");
-                return 0;
-            }
-
+            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' );
+            $msg = "$email has no right to own ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^resolve$/i ) {
 
             # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
-            unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' ) ) {
-                $RT::Logger->debug("Unprivileged users have no right to resolve ticket in queue '$qname'");
-                return 0;
-            }
-
+            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' );
+            $msg = "$email has no right to resolve ticket $tid in queue $qname";
         }
         else {
             $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
-            return 0;
+            return;
         }
     }
 
     # We're creating a ticket
-    elsif ( $args{'Queue'} && $args{'Queue'}->Id ) {
+    elsif ( $args{'Action'} =~ /^(comment|correspond)$/i ) {
         my $qname = $args{'Queue'}->Name;
 
         # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
-        unless ( $CurrentUser->PrincipalObj->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' ) ) {
-            $RT::Logger->debug("Unprivileged users have no right to create ticket in queue '$qname'");
-            return 0;
-        }
+        return 1 if $principal->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' );
+        $msg = "$email has no right to create tickets in queue $qname";
     }
+    else {
+        $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown with no ticket");
+        return;
+    }
+
 
-    return 1;
+    MailError(
+        To          => $args{ErrorsTo},
+        Subject     => "Permission Denied",
+        Explanation => $msg,
+        MIMEObj     => $args{Message},
+    );
+    FAILURE( $msg );
 }
 
 RT::Base->_ImportOverlays();
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index 4e1dba8..d87b3f8 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -194,8 +194,8 @@ EOF
     $u->Load("doesnotexist\@@{[RT->Config->Get('rtname')]}");
     ok( $u->Id, "user was created by failed ticket submission");
 
-    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist\@\S+ tried to submit a message to General without permission/);
+    $m->next_warning_like(qr/Permission Denied: doesnotexist\@\S+ has no right to create tickets in queue General/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist\@\S+ has no right to create tickets in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -253,8 +253,8 @@ EOF
     my $u = RT::User->new(RT->SystemUser);
     $u->Load('doesnotexist-2@'.RT->Config->Get('rtname'));
     ok( $u->Id, "user was created by ticket correspondence submission");
-    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist-2\@\S+ tried to submit a message to General without permission/);
+    $m->next_warning_like(qr/Permission Denied: doesnotexist-2\@\S+ has no right to reply to ticket $ticket_id in queue General/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist-2\@\S+ has no right to reply to ticket $ticket_id in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -337,8 +337,8 @@ EOF
     my $u = RT::User->new(RT->SystemUser);
     $u->Load('doesnotexist-3@'.RT->Config->Get('rtname'));
     ok( $u->Id, "user was created by ticket comment submission");
-    $m->next_warning_like(qr/You do not have permission to communicate with RT/);
-    $m->next_warning_like(qr/Could not record email: doesnotexist-3\@\S+ tried to submit a message to General without permission/);
+    $m->next_warning_like(qr/Permission Denied: doesnotexist-3\@\S+ has no right to comment on ticket $ticket_id in queue General/);
+    $m->next_warning_like(qr/Could not record email: doesnotexist-3\@\S+ has no right to comment on ticket $ticket_id in queue General/);
     $m->no_leftover_warnings_ok;
 }
 
@@ -729,8 +729,8 @@ ok( $status, "successfuly granted right: $msg" );
 my $ace_id = $status;
 ok( $user->HasRight( Right => 'ReplyToTicket', Object => $tick ), "User can reply to ticket" );
 
-$m->next_warning_like(qr/Permission Denied: You do not have permission to communicate with RT/);
-$m->next_warning_like(qr/Could not record email: ext-mailgate@\S+ tried to submit a message to ext-mailgate without permission/);
+$m->next_warning_like(qr/ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
+$m->next_warning_like(qr/Could not record email: ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
 $m->no_leftover_warnings_ok;
 
 $! = 0;
@@ -767,8 +767,8 @@ DBIx::SearchBuilder::Record::Cachable->FlushCache;
 cmp_ok( $tick->Owner, '!=', $user->id, "we didn't change owner" );
 is( $tick->Transactions->Count, 3, "no transactions added, user can't take ticket first" );
 
-$m->next_warning_like(qr/That user may not own tickets in that queue/);
-$m->next_warning_like(qr/Could not record email: Ticket not taken/);
+$m->next_warning_like(qr/ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
+$m->next_warning_like(qr/Could not record email: ext-mailgate\@localhost has no right to own ticket $id in queue ext-mailgate/);
 $m->no_leftover_warnings_ok;
 
 # revoke ReplyToTicket right

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

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

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

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

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

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

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

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

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

commit 4a8378024d49bcddd5c9aca3de6fa74f124e3529
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 806a51a..a29e132 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -62,6 +62,7 @@ use RT::Util 'safe_run_child';
 use File::Spec;
 use MIME::Words ();
 use Scope::Upper qw/unwind HERE/;
+use 5.010;
 
 =head1 NAME
 
@@ -153,18 +154,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'},
@@ -209,7 +203,6 @@ sub Gateway {
 
     my $CurrentUser = GetCurrentUser(
         ErrorsTo      => $ErrorsTo,
-        MailPlugins   => \@mail_plugins,
         Message       => $Message,
         RawMessageRef => \$args{message},
         Ticket        => $SystemTicket,
@@ -221,7 +214,6 @@ sub Gateway {
     CheckACL(
         Action        => $actions[0],
         ErrorsTo      => $ErrorsTo,
-        MailPlugins   => \@mail_plugins,
         Message       => $Message,
         CurrentUser   => $CurrentUser,
         Ticket        => $SystemTicket,
@@ -343,30 +335,43 @@ sub IsCorrectAction {
     return ( 1, @actions );
 }
 
-sub _LoadPlugins {
-    my @mail_plugins = @_;
-
-    my @res;
-    foreach my $plugin (@mail_plugins) {
-        if ( ref($plugin) eq "CODE" ) {
-            push @res, $plugin;
-        } elsif ( !ref $plugin ) {
-            my $Class = $plugin;
-            $Class = "RT::Interface::Email::" . $Class
-                unless $Class =~ /^RT::/;
-            $Class->require or
-                do { $RT::Logger->error("Couldn't load $Class: $@"); next };
-
-            unless ( $Class->DOES( "RT::Interface::Email::Role" ) ) {
-                $RT::Logger->crit( "$Class does not implement RT::Interface::Email::Role.  Mail plugins from RT 4.2 and earlier are not forward-compatible with RT 4.4.");
-                next;
+sub Plugins {
+    my %args = (
+        Code => 0,
+        Method => undef,
+        @_
+    );
+    state @PLUGINS;
+
+    unless (@PLUGINS) {
+        my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
+        push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
+
+        foreach my $plugin (@mail_plugins) {
+            if ( ref($plugin) eq "CODE" ) {
+                push @PLUGINS, $plugin;
+            } elsif ( !ref $plugin ) {
+                my $Class = $plugin;
+                $Class = "RT::Interface::Email::" . $Class
+                    unless $Class =~ /^RT::/;
+                $Class->require or
+                    do { $RT::Logger->error("Couldn't load $Class: $@"); next };
+
+                unless ( $Class->DOES( "RT::Interface::Email::Role" ) ) {
+                    $RT::Logger->crit( "$Class does not implement RT::Interface::Email::Role.  Mail plugins from RT 4.2 and earlier are not forward-compatible with RT 4.4.");
+                    next;
+                }
+                push @PLUGINS, $Class;
+            } else {
+                $RT::Logger->crit( "$plugin - is not class name or code reference");
             }
-            push @res, $Class;
-        } else {
-            $RT::Logger->crit( "$plugin - is not class name or code reference");
         }
     }
-    return @res;
+
+    my @list = @PLUGINS;
+    @list = grep {not ref} @list unless $args{Code};
+    @list = grep {$_} map {ref $_ ? $_ : $_->can($args{Method})} @list if $args{Method};
+    return @list;
 }
 
 =head3 GetCurrentUser
@@ -376,7 +381,6 @@ sub _LoadPlugins {
 sub GetCurrentUser {
     my %args = (
         ErrorsTo      => undef,
-        MailPlugins   => [],
         Message       => undef,
         RawMessageRef => undef,
         Ticket        => undef,
@@ -385,15 +389,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},
@@ -429,7 +425,6 @@ sub CheckACL {
     my %args = (
         Action        => undef,
         ErrorsTo      => undef,
-        MailPlugins   => [],
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -437,10 +432,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 9edfe8cea4433a4af1cb9f06c2e6294bbef44144
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 26 20:03:59 2014 -0500

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

diff --git a/bin/rt-mailgate.in b/bin/rt-mailgate.in
index 9b0b8d4..8498243 100644
--- a/bin/rt-mailgate.in
+++ b/bin/rt-mailgate.in
@@ -329,15 +329,15 @@ Usual invocation (from MTA):
 
 =item C<--action>
 
-Specifies what happens to email sent to this alias.  The avaliable
-basic actions are: C<correspond>, C<comment>.
-
-
-If you've set the RT configuration variable B<< C<UnsafeEmailCommands> >>,
-C<take> and C<resolve> are also available.  You can execute two or more
-actions on a single message using a C<-> separated list.  RT will execute
-the actions in the listed order.  For example you can use C<take-comment>,
-C<correspond-resolve> or C<take-comment-resolve> as actions.
+Specifies what happens to email sent to this alias.  The avaliable basic
+actions are: C<correspond>, C<comment>.  Additional actions, such as
+C<take> or C<resolve>, may be available depending on your local
+C<@MailPlugins> configuration.
+
+You can execute two or more actions on a single message using a C<->
+separated list.  RT will execute the actions in the listed order.  For
+example you can use C<take-comment>, C<correspond-resolve> or
+C<take-comment-resolve> as actions.
 
 Note that C<take> and C<resolve> actions ignore message text if used
 alone.  Include a  C<comment> or C<correspond> action if you want RT
diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
index 16befc4..9b59f4b 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -47,6 +47,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 87b9520..f650646 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -474,15 +474,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 a29e132..19a19f4 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -125,14 +125,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(
@@ -222,119 +220,23 @@ sub Gateway {
 
     $head->replace('X-RT-Interface' => 'Email');
 
-    # if plugin's updated SystemTicket then update arguments
-    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
-
     my $Ticket = RT::Ticket->new($CurrentUser);
-
-    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
-    {
-
-        my @Cc;
-        my @Requestors = ( $CurrentUser->id );
-
-        if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
-            @Cc = ParseCcAddressesFromHead(
-                Head        => $head,
-                CurrentUser => $CurrentUser,
-                QueueObj    => $SystemQueueObj
-            );
-        }
-
-        # ExtractTicketId may have been overridden, and edited the Subject
-        my $NewSubject = Encode::decode( "UTF-8", $head->get('Subject') );
-        chomp $NewSubject;
-
-        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
-            Queue     => $SystemQueueObj->Id,
-            Subject   => $NewSubject,
-            Requestor => \@Requestors,
-            Cc        => \@Cc,
-            MIMEObj   => $Message
+    $Ticket->Load( $SystemTicket->Id );
+
+    for my $action (@actions) {
+        HandleAction(
+            Action      => $action,
+            ErrorsTo    => $ErrorsTo,
+            Subject     => $Subject,
+            Message     => $Message,
+            Ticket      => $Ticket,
+            TicketId    => $args{ticket},
+            Queue       => $SystemQueueObj,
         );
-        if ( $id == 0 ) {
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "Ticket creation failed: $Subject",
-                Explanation => $ErrStr,
-                MIMEObj     => $Message
-            );
-            FAILURE("Ticket creation From: $From failed: $ErrStr", $Ticket );
-        }
-
-        # strip comments&corresponds from the actions we don't need
-        # to record them if we've created the ticket just now
-        @actions = grep !/^(comment|correspond)$/, @actions;
-        $args{'ticket'} = $id;
-
-    } elsif ( $args{'ticket'} ) {
-
-        $Ticket->Load( $args{'ticket'} );
-        unless ( $Ticket->Id ) {
-            my $error = "Could not find a ticket with id " . $args{'ticket'};
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "Message not recorded: $Subject",
-                Explanation => $error,
-                MIMEObj     => $Message
-            );
-
-            FAILURE( $error );
-        }
-        $args{'ticket'} = $Ticket->id;
-    } else {
-        SUCCESS( $Ticket );
-    }
-
-    # }}}
-
-    my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
-    foreach my $action (@actions) {
-
-        #   If the action is comment, add a comment.
-        if ( $action =~ /^(?:comment|correspond)$/i ) {
-            my $method = ucfirst lc $action;
-            my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );
-            unless ($status) {
-
-                #Warn the sender that we couldn't actually submit the comment.
-                MailError(
-                    To          => $ErrorsTo,
-                    Subject     => "Message not recorded ($method): $Subject",
-                    Explanation => $msg,
-                    MIMEObj     => $Message
-                );
-                FAILURE( "Message From: $From not recorded: $msg", $Ticket );
-            }
-        } elsif ($unsafe_actions) {
-            _RunUnsafeAction(
-                Action      => $action,
-                ErrorsTo    => $ErrorsTo,
-                Message     => $Message,
-                Ticket      => $Ticket,
-                CurrentUser => $CurrentUser,
-            );
-        }
     }
     SUCCESS( $Ticket );
 }
 
-=head3 IsCorrectAction
-
-Returns a list of valid actions we've found for this message
-
-=cut
-
-sub IsCorrectAction {
-    my $action = shift;
-    my @actions = grep $_, split /-/, $action;
-    return ( 0, '(no value)' ) unless @actions;
-    foreach ( @actions ) {
-        return ( 0, $_ ) unless /^(?:comment|correspond|take|resolve)$/;
-    }
-    return ( 1, @actions );
-}
-
 sub Plugins {
     my %args = (
         Code => 0,
@@ -346,6 +248,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" ) {
@@ -454,36 +357,23 @@ sub CheckACL {
 }
 
 
-=head3 ParseCcAddressesFromHead HASH
-
-Takes a hash containing QueueObj, Head and CurrentUser objects.
-Returns a list of all email addresses in the To and Cc
-headers b<except> the current Queue's email addresses, the CurrentUser's
-email address  and anything that the configuration sub RT::IsRTAddress matches.
-
-=cut
-
-sub ParseCcAddressesFromHead {
+sub HandleAction {
     my %args = (
-        Head        => undef,
-        QueueObj    => undef,
-        CurrentUser => undef,
+        Action   => undef,
+        ErrorsTo => undef,
+        Subject  => undef,
+        Message  => undef,
+        Ticket   => undef,
+        TicketId => undef,
+        Queue    => undef,
         @_
     );
 
-    my $current_address = lc $args{'CurrentUser'}->EmailAddress;
-    my $user = $args{'CurrentUser'}->UserObj;
-
-    return
-        grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ),
-        map lc $user->CanonicalizeEmailAddress( $_->address ),
-        map RT::EmailParser->CleanupAddresses( Email::Address->parse(
-              Encode::decode( "UTF-8", $args{'Head'}->get( $_ ) ) ) ),
-        qw(To Cc);
+    my ($code) = Plugins(Method => "Handle" . ucfirst(delete $args{Action}));
+    $code->(%args);
 }
 
 
-
 =head3 ParseSenderAddressFromHead HEAD
 
 Takes a MIME::Header object. Returns (user at host, friendly name, errors)
@@ -785,50 +675,6 @@ sub ParseTicketId {
     return $id;
 }
 
-sub _RunUnsafeAction {
-    my %args = (
-        Action      => undef,
-        ErrorsTo    => undef,
-        Message     => undef,
-        Ticket      => undef,
-        CurrentUser => undef,
-        @_
-    );
-
-    my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
-
-    if ( $args{'Action'} =~ /^take$/i ) {
-        my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{'CurrentUser'}->id );
-        unless ($status) {
-            MailError(
-                To          => $args{'ErrorsTo'},
-                Subject     => "Ticket not taken",
-                Explanation => $msg,
-                MIMEObj     => $args{'Message'}
-            );
-            FAILURE( "Ticket not taken, by email From: $From" );
-        }
-    } elsif ( $args{'Action'} =~ /^resolve$/i ) {
-        my $new_status = $args{'Ticket'}->FirstInactiveStatus;
-        if ($new_status) {
-            my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status);
-            unless ($status) {
-
-                #Warn the sender that we couldn't actually submit the comment.
-                MailError(
-                    To          => $args{'ErrorsTo'},
-                    Subject     => "Ticket not resolved",
-                    Explanation => $msg,
-                    MIMEObj     => $args{'Message'}
-                );
-                FAILURE( "Ticket not resolved, by email From: $From" );
-            }
-        }
-    } else {
-        FAILURE( "Not supported unsafe action $args{'Action'}, by email From: $From", $args{'Ticket'} );
-    }
-}
-
 =head3 MailError PARAM HASH
 
 Sends an error message. Takes a param hash:
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
new file mode 100644
index 0000000..239c47e
--- /dev/null
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -0,0 +1,146 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Interface::Email::Action::Defaults;
+
+use strict;
+use warnings;
+
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
+use RT::Interface::Email;
+
+sub _HandleCreate {
+    my %args = (
+        ErrorsTo    => undef,
+        Subject     => undef,
+        Message     => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    my $head = $args{Message}->head;
+
+    my @Cc;
+    my @Requestors = ( $args{Ticket}->CurrentUser->id );
+    if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
+        my $user = $args{Ticket}->CurrentUser->UserObj;
+        my $current_address = lc $user->EmailAddress;
+
+        @Cc =
+            grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ),
+            map lc $user->CanonicalizeEmailAddress( $_->address ),
+            map RT::EmailParser->CleanupAddresses( Email::Address->parse(
+                  Encode::decode( "UTF-8", $head->get( $_ ) ) ) ),
+            qw(To Cc);
+    }
+
+    # ExtractTicketId may have been overridden, and edited the Subject
+    my $subject = Encode::decode( "UTF-8", $head->get('Subject') );
+    chomp $subject;
+
+    my ( $id, $Transaction, $ErrStr ) = $args{Ticket}->Create(
+        Queue     => $args{Queue}->Id,
+        Subject   => $subject,
+        Requestor => \@Requestors,
+        Cc        => \@Cc,
+        MIMEObj   => $args{Message},
+    );
+    return if $id;
+
+    MailError(
+        To          => $args{ErrorsTo},
+        Subject     => "Ticket creation failed: $args{Subject}",
+        Explanation => $ErrStr,
+        MIMEObj     => $args{Message},
+    );
+    FAILURE("Ticket creation failed: $ErrStr", $args{Ticket} );
+}
+
+sub HandleComment {
+    _HandleEither( @_, Action => "Comment" );
+}
+
+sub HandleCorrespond {
+    _HandleEither( @_, Action => "Correspond" );
+}
+
+sub _HandleEither {
+    my %args = (
+        Action      => undef,
+        ErrorsTo    => undef,
+        Message     => undef,
+        Subject     => undef,
+        Ticket      => undef,
+        TicketId    => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    return _HandleCreate(@_) unless $args{TicketId};
+
+    unless ( $args{Ticket}->Id ) {
+        my $error = "Could not find a ticket with id " . $args{TicketId};
+        MailError(
+            To          => $args{ErrorsTo},
+            Subject     => "Message not recorded: $args{Subject}",
+            Explanation => $error,
+            MIMEObj     => $args{Message}
+        );
+        FAILURE( $error );
+    }
+
+    my $action = ucfirst $args{Action};
+    my ( $status, $msg ) = $args{Ticket}->$action( MIMEObj => $args{Message} );
+    return if $status;
+}
+
+1;
+
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
new file mode 100644
index 0000000..0b4a688
--- /dev/null
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -0,0 +1,96 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Interface::Email::Action::Resolve;
+
+use strict;
+use warnings;
+
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
+sub HandleResolve {
+    my %args = (
+        ErrorsTo    => undef,
+        Message     => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    unless ( $args{Ticket}->Id ) {
+        my $error = "Could not find a ticket with id " . $args{TicketId};
+        MailError(
+            To          => $args{ErrorsTo},
+            Subject     => "Message not recorded: $args{Subject}",
+            Explanation => $error,
+            MIMEObj     => $args{Message}
+        );
+        FAILURE( $error );
+    }
+
+    my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
+
+    my $new_status = $args{'Ticket'}->FirstInactiveStatus;
+    return unless $new_status;
+
+    my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status);
+    return if $status;
+
+    # Warn the sender that we couldn't actually resolve the ticket
+    MailError(
+        To          => $args{'ErrorsTo'},
+        Subject     => "Ticket not resolved",
+        Explanation => $msg,
+        MIMEObj     => $args{'Message'}
+    );
+    FAILURE( "Ticket not resolved, by email From: $From" );
+}
+
+1;
+
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
new file mode 100644
index 0000000..8546828
--- /dev/null
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -0,0 +1,92 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Interface::Email::Action::Take;
+
+use strict;
+use warnings;
+
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
+sub HandleTake {
+    my %args = (
+        ErrorsTo    => undef,
+        Message     => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    unless ( $args{Ticket}->Id ) {
+        my $error = "Could not find a ticket with id " . $args{TicketId};
+        MailError(
+            To          => $args{ErrorsTo},
+            Subject     => "Message not recorded: $args{Subject}",
+            Explanation => $error,
+            MIMEObj     => $args{Message}
+        );
+        FAILURE( $error );
+    }
+
+    my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
+
+    my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{Ticket}->CurrentUser->id );
+    return if $status;
+
+    MailError(
+        To          => $args{'ErrorsTo'},
+        Subject     => "Ticket not taken",
+        Explanation => $msg,
+        MIMEObj     => $args{'Message'}
+    );
+    FAILURE( "Ticket not taken, by email From: $From" );
+}
+
+1;
+
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index b12fd55..73a4ea8 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -1244,7 +1244,7 @@ sub FirstActiveStatus {
 Returns the first inactive status that the ticket could transition to,
 according to its current Queue's lifecycle.  May return undef if there
 is no such possible status to transition to, or we are already in it.
-This is used in resolve action in UnsafeEmailCommands, for instance.
+This is used in L<RT::Interface::Email::Action::Resolve>, for instance.
 
 =cut
 
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index 4e1c9c8..e955233 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 
-use RT::Test config => 'Set( $UnsafeEmailCommands, 1);', tests => undef, actual_server => 1;
+use RT::Test config => 'Set( @MailPlugins, "Auth::MailFrom", "Action::Take", "Action::Resolve");', tests => undef, actual_server => 1;
 my ($baseurl, $m) = RT::Test->started_ok;
 
 use RT::Tickets;
@@ -600,10 +600,6 @@ EOF
 my ($val,$msg) = $everyone_group->PrincipalObj->RevokeRight(Right => 'CreateTicket');
 ok ($val, $msg);
 
-SKIP: {
-skip "Advanced mailgate actions require an unsafe configuration", 47
-    unless RT->Config->Get('UnsafeEmailCommands');
-
 # create new queue to be shure we don't mess with rights
 use RT::Queue;
 my $queue = RT::Queue->new(RT->SystemUser);
@@ -813,7 +809,5 @@ is( $tick->Transactions->Count, 6, "transactions added" );
 
 $m->no_warnings_ok;
 
-};
-
 undef $m;
 done_testing;

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

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

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

commit dcf135133c0576e8ed35e65bd5786f2ab5328536
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 8ebfabc..4745a65 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -111,7 +111,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    = (
@@ -185,11 +184,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 );
@@ -200,7 +205,6 @@ sub Gateway {
         unless $SystemTicket->id || $SystemQueueObj->id;
 
     my $CurrentUser = GetCurrentUser(
-        ErrorsTo      => $ErrorsTo,
         Message       => $Message,
         RawMessageRef => \$args{message},
         Ticket        => $SystemTicket,
@@ -211,7 +215,6 @@ sub Gateway {
     # may have gotten rights by the time they happen.
     CheckACL(
         Action        => $actions[0],
-        ErrorsTo      => $ErrorsTo,
         Message       => $Message,
         CurrentUser   => $CurrentUser,
         Ticket        => $SystemTicket,
@@ -226,7 +229,6 @@ sub Gateway {
     for my $action (@actions) {
         HandleAction(
             Action      => $action,
-            ErrorsTo    => $ErrorsTo,
             Subject     => $Subject,
             Message     => $Message,
             Ticket      => $Ticket,
@@ -284,7 +286,6 @@ sub Plugins {
 
 sub GetCurrentUser {
     my %args = (
-        ErrorsTo      => undef,
         Message       => undef,
         RawMessageRef => undef,
         Ticket        => undef,
@@ -307,10 +308,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");
 }
@@ -328,7 +327,6 @@ sub GetCurrentUser {
 sub CheckACL {
     my %args = (
         Action        => undef,
-        ErrorsTo      => undef,
         Message       => undef,
         CurrentUser   => undef,
         Ticket        => undef,
@@ -338,7 +336,6 @@ sub CheckACL {
 
     for my $Code ( Plugins( Method => "CheckACL" ) ) {
         return if $Code->(
-            ErrorsTo      => $args{ErrorsTo},
             Message       => $args{Message},
             CurrentUser   => $args{CurrentUser},
             Action        => $args{Action},
@@ -349,10 +346,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}" );
 }
@@ -361,7 +356,6 @@ sub CheckACL {
 sub HandleAction {
     my %args = (
         Action   => undef,
-        ErrorsTo => undef,
         Subject  => undef,
         Message  => undef,
         Ticket   => undef,
@@ -506,7 +500,6 @@ sub IsMachineGeneratedMail {
                 To          => $owner_mail,
                 Subject     => "RT Bounce: ".$args{'Subject'},
                 Explanation => "RT thinks this message may be a bounce",
-                MIMEObj     => $args{Message}
             );
         }
 
@@ -690,8 +683,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';
@@ -711,7 +702,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',
@@ -730,7 +720,6 @@ sub MailError {
     my %entity_args = (
         Type                    => "multipart/mixed",
         From                    => Encode::encode( "UTF-8", $args{'From'} ),
-        Bcc                     => Encode::encode( "UTF-8", $args{'Bcc'} ),
         To                      => Encode::encode( "UTF-8", $args{'To'} ),
         Subject                 => EncodeToMIME( String => $args{'Subject'} ),
         'X-RT-Loop-Prevention:' => Encode::encode( "UTF-8", RT->Config->Get('rtname') ),
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
index 239c47e..105b356 100644
--- a/lib/RT/Interface/Email/Action/Defaults.pm
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -58,7 +58,6 @@ use RT::Interface::Email;
 
 sub _HandleCreate {
     my %args = (
-        ErrorsTo    => undef,
         Subject     => undef,
         Message     => undef,
         Ticket      => undef,
@@ -96,10 +95,8 @@ sub _HandleCreate {
     return if $id;
 
     MailError(
-        To          => $args{ErrorsTo},
         Subject     => "Ticket creation failed: $args{Subject}",
         Explanation => $ErrStr,
-        MIMEObj     => $args{Message},
     );
     FAILURE("Ticket creation failed: $ErrStr", $args{Ticket} );
 }
@@ -115,7 +112,6 @@ sub HandleCorrespond {
 sub _HandleEither {
     my %args = (
         Action      => undef,
-        ErrorsTo    => undef,
         Message     => undef,
         Subject     => undef,
         Ticket      => undef,
@@ -129,10 +125,8 @@ sub _HandleEither {
     unless ( $args{Ticket}->Id ) {
         my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
-            To          => $args{ErrorsTo},
             Subject     => "Message not recorded: $args{Subject}",
             Explanation => $error,
-            MIMEObj     => $args{Message}
         );
         FAILURE( $error );
     }
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
index 0b4a688..daaa3dd 100644
--- a/lib/RT/Interface/Email/Action/Resolve.pm
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -56,7 +56,6 @@ with 'RT::Interface::Email::Role';
 
 sub HandleResolve {
     my %args = (
-        ErrorsTo    => undef,
         Message     => undef,
         Ticket      => undef,
         Queue       => undef,
@@ -66,10 +65,8 @@ sub HandleResolve {
     unless ( $args{Ticket}->Id ) {
         my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
-            To          => $args{ErrorsTo},
             Subject     => "Message not recorded: $args{Subject}",
             Explanation => $error,
-            MIMEObj     => $args{Message}
         );
         FAILURE( $error );
     }
@@ -84,10 +81,8 @@ sub HandleResolve {
 
     # Warn the sender that we couldn't actually resolve the ticket
     MailError(
-        To          => $args{'ErrorsTo'},
         Subject     => "Ticket not resolved",
         Explanation => $msg,
-        MIMEObj     => $args{'Message'}
     );
     FAILURE( "Ticket not resolved, by email From: $From" );
 }
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
index 8546828..60b9b15 100644
--- a/lib/RT/Interface/Email/Action/Take.pm
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -56,7 +56,6 @@ with 'RT::Interface::Email::Role';
 
 sub HandleTake {
     my %args = (
-        ErrorsTo    => undef,
         Message     => undef,
         Ticket      => undef,
         Queue       => undef,
@@ -66,10 +65,8 @@ sub HandleTake {
     unless ( $args{Ticket}->Id ) {
         my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
-            To          => $args{ErrorsTo},
             Subject     => "Message not recorded: $args{Subject}",
             Explanation => $error,
-            MIMEObj     => $args{Message}
         );
         FAILURE( $error );
     }
@@ -80,10 +77,8 @@ sub HandleTake {
     return if $status;
 
     MailError(
-        To          => $args{'ErrorsTo'},
         Subject     => "Ticket not taken",
         Explanation => $msg,
-        MIMEObj     => $args{'Message'}
     );
     FAILURE( "Ticket not taken, by email From: $From" );
 }
diff --git a/lib/RT/Interface/Email/Authz/Default.pm b/lib/RT/Interface/Email/Authz/Default.pm
index cdc11f0..5815672 100644
--- a/lib/RT/Interface/Email/Authz/Default.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -56,7 +56,6 @@ with 'RT::Interface::Email::Role';
 
 sub CheckACL {
     my %args = (
-        ErrorsTo    => undef,
         Message     => undef,
         CurrentUser => undef,
         Ticket      => undef,
@@ -127,10 +126,8 @@ EOT
 
 
     MailError(
-        To          => $args{ErrorsTo},
         Subject     => "Permission Denied",
         Explanation => $msg,
-        MIMEObj     => $args{Message},
     );
     FAILURE( $msg );
 }

commit 1175342160b9f010922f734b9e9f3811f0b8634e
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 bd321ee..085d7d5 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -677,22 +677,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 940073d9d4a5c104caff4b6d358b71b1fb646fe3
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 085d7d5..ddc4d09 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -676,7 +676,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 cad86d2..68aff35 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 ddb91e4..6f1cb24 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 4745a65..25a5e7e 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -241,17 +241,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" ) {
@@ -272,6 +272,7 @@ sub Plugins {
                 $RT::Logger->crit( "$plugin - is not class name or code reference");
             }
         }
+        $INIT = 1;
     }
 
     my @list = @PLUGINS;
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 3e07b1f..d42fab1 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -64,10 +64,7 @@ This code is independant from code that encrypts/sign outgoing emails, so
 it's possible to decrypt data without bringing in encryption. To enable
 it put the module in the mail plugins list:
 
-    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filters...);
-
-C<Auth::Crypt> will not function without C<Auth::MailFrom> listed before
-it.
+    Set(@MailPlugins, 'Auth::Crypt', ...other filters...);
 
 =head3 GnuPG
 
diff --git a/t/mail/gateway.t b/t/mail/gateway.t
index e955233..b8a8b46 100644
--- a/t/mail/gateway.t
+++ b/t/mail/gateway.t
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 
-use RT::Test config => 'Set( @MailPlugins, "Auth::MailFrom", "Action::Take", "Action::Resolve");', tests => undef, actual_server => 1;
+use RT::Test config => 'Set( @MailPlugins, "Action::Take", "Action::Resolve");', tests => undef, actual_server => 1;
 my ($baseurl, $m) = RT::Test->started_ok;
 
 use RT::Tickets;
diff --git a/t/mail/gnupg-bad.t b/t/mail/gnupg-bad.t
index 570501c..00930c9 100644
--- a/t/mail/gnupg-bad.t
+++ b/t/mail/gnupg-bad.t
@@ -10,7 +10,7 @@ use RT::Test::GnuPG
     ),
   };
 
-RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
+RT->Config->Set( 'MailPlugins' => 'Auth::Crypt' );
 
 my ($baseurl, $m) = RT::Test->started_ok;
 

commit 51266115b85de0a625ba234467af2d82dda63cd6
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 f650646..ad813b2 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2338,9 +2338,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 ddc4d09..096b384 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -676,15 +676,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 68aff35..a2b21df 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 6f1cb24..b8d3057 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -93,9 +93,6 @@ Set to true value to enable this subsystem:
         ... other options ...
     );
 
-However, note that you B<must> add the 'Auth::Crypt' email filter to enable
-the handling of incoming encrypted/signed messages.
-
 =head3 Format of outgoing messages
 
 The format of outgoing messages can be controlled using the
@@ -235,15 +232,6 @@ As well, encryption is enabled for autoreplies and other notifications when
 an encypted message enters system via mailgate interface even if queue's
 option is disabled.
 
-=head2 Handling incoming messages
-
-To enable handling of encrypted and signed message in the RT you should add
-'Auth::Crypt' mail plugin.
-
-    Set(@MailPlugins, 'Auth::Crypt', ...other filter...);
-
-See also `perldoc lib/RT/Interface/Email/Auth/Crypt.pm`.
-
 =head2 Encrypting to untrusted keys
 
 Due to limitations of GnuPG, it's impossible to encrypt to an untrusted key,
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index d42fab1..a948aa4 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -60,12 +60,6 @@ RT::Interface::Email::Auth::Crypt - decrypting and verifying protected emails
 This mail plugin decrypts and verifies incoming emails. Supported
 encryption protocols are GnuPG and SMIME.
 
-This code is independant from code that encrypts/sign outgoing emails, so
-it's possible to decrypt data without bringing in encryption. To enable
-it put the module in the mail plugins list:
-
-    Set(@MailPlugins, 'Auth::Crypt', ...other filters...);
-
 =head3 GnuPG
 
 To use the gnupg-secured mail gateway, you need to do the following:
diff --git a/lib/RT/Test/GnuPG.pm b/lib/RT/Test/GnuPG.pm
index e864845..a2ae426 100644
--- a/lib/RT/Test/GnuPG.pm
+++ b/lib/RT/Test/GnuPG.pm
@@ -106,7 +106,6 @@ Set(\%GnuPG, (
     OutgoingMessagesFormat => 'RFC',
 ));
 Set(\%GnuPGOptions => \%{ $dumped_gnupg_options });
-Set(\@MailPlugins => qw(Auth::MailFrom Auth::Crypt));
 };
 
 }
diff --git a/lib/RT/Test/SMIME.pm b/lib/RT/Test/SMIME.pm
index d39c4b4..7c94380 100644
--- a/lib/RT/Test/SMIME.pm
+++ b/lib/RT/Test/SMIME.pm
@@ -101,7 +101,6 @@ sub bootstrap_more_config {
             Keyring => q{$keyring},
             CAPath  => q{$ca},
         );
-        Set(\@MailPlugins => qw(Auth::MailFrom Auth::Crypt));
     };
 
 }
diff --git a/t/mail/gnupg-bad.t b/t/mail/gnupg-bad.t
index 00930c9..a9fd45a 100644
--- a/t/mail/gnupg-bad.t
+++ b/t/mail/gnupg-bad.t
@@ -10,8 +10,6 @@ use RT::Test::GnuPG
     ),
   };
 
-RT->Config->Set( 'MailPlugins' => 'Auth::Crypt' );
-
 my ($baseurl, $m) = RT::Test->started_ok;
 
 $m->login;

commit 770b585139366c8f27b254bef3c3a7f16285b1b3
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 096b384..5e469f9 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -676,7 +676,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 25a5e7e..a770170 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -52,6 +52,7 @@ use strict;
 use warnings;
 use 5.010;
 
+use RT::Interface::Email::Crypt;
 use Email::Address;
 use MIME::Entity;
 use RT::EmailParser;
@@ -155,6 +156,21 @@ sub Gateway {
     my $SystemQueueObj = RT::Queue->new( RT->SystemUser );
     $SystemQueueObj->Load( $args{'queue'} );
 
+    for my $Code ( Plugins(Method => "BeforeDecrypt") ) {
+        $Code->(
+            Message       => $Message,
+            RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
+        );
+    }
+
+    RT::Interface::Email::Crypt::VerifyDecrypt(
+        Message       => $Message,
+        RawMessageRef => \$args{'message'},
+        Queue         => $SystemQueueObj,
+    );
+
     for my $Code ( Plugins(Method => "BeforeDecode") ) {
         $Code->(
             Message       => $Message,
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Crypt.pm
similarity index 86%
rename from lib/RT/Interface/Email/Auth/Crypt.pm
rename to lib/RT/Interface/Email/Crypt.pm
index a948aa4..7186192 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Crypt.pm
@@ -46,65 +46,25 @@
 #
 # END BPS TAGGED BLOCK }}}
 
-package RT::Interface::Email::Auth::Crypt;
+package RT::Interface::Email::Crypt;
 
 use strict;
 use warnings;
 
 =head1 NAME
 
-RT::Interface::Email::Auth::Crypt - decrypting and verifying protected emails
-
-=head2 DESCRIPTION
-
-This mail plugin decrypts and verifies incoming emails. Supported
-encryption protocols are GnuPG and SMIME.
-
-=head3 GnuPG
-
-To use the gnupg-secured mail gateway, you need to do the following:
-
-Set up a GnuPG key directory with a pubring containing only the keys
-you care about and specify the following in your SiteConfig.pm
-
-    Set(%GnuPGOptions, homedir => '/opt/rt4/var/data/GnuPG');
-
-Read also: L<RT::Crypt> and L<RT::Crypt::GnuPG>.
-
-=head3 SMIME
-
-To use the SMIME-secured mail gateway, you need to do the following:
-
-Set up a SMIME key directory with files containing keys for queues'
-addresses and specify the following in your SiteConfig.pm
-
-    Set(%SMIME,
-        Enable => 1,
-        OpenSSL => '/usr/bin/openssl',
-        Keyring => '/opt/rt4/var/data/smime',
-        CAPath  => '/opt/rt4/var/data/smime/signing-ca.pem',
-        Passphrase => {
-            'queue.address at example.com' => 'passphrase',
-            '' => 'fallback',
-        },
-    );
-
-Read also: L<RT::Crypt> and L<RT::Crypt::SMIME>.
+RT::Interface::Email::Crypt - decrypting and verifying protected emails
 
 =cut
 
 use RT::Crypt;
 use RT::EmailParser ();
 
-use Role::Basic 'with';
-with 'RT::Interface::Email::Role';
-
-sub BeforeDecode {
+sub VerifyDecrypt {
     my %args = (
         Message       => undef,
         RawMessageRef => undef,
         Queue         => undef,
-        Actions       => undef,
         @_
     );
 

commit 43a1ac6f5a8d3282e3a8e369a546ae6ba70aaffe
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 9b59f4b..84a3934 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -53,6 +53,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 ad813b2..cb29450 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2345,9 +2345,6 @@ used. Use the C<Outgoing> option to set a security protocol that should
 be used in outgoing emails.  At this moment, only one protocol can be
 used to protect outgoing emails.
 
-Set C<RejectOnUnencrypted> to 1 if all incoming email must be
-properly encrypted.  All unencrypted emails will be rejected by RT.
-
 Set C<RejectOnMissingPrivateKey> to 0 if you don't want to reject
 emails encrypted for key RT doesn't have and can not decrypt.
 
@@ -2369,7 +2366,6 @@ Set( %Crypt,
     Incoming                  => undef, # ['GnuPG', 'SMIME']
     Outgoing                  => undef, # 'SMIME'
 
-    RejectOnUnencrypted       => 0,
     RejectOnMissingPrivateKey => 1,
     RejectOnBadData           => 1,
 
diff --git a/lib/RT/Interface/Email/Authz/RequireEncrypted.pm b/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
new file mode 100644
index 0000000..b96cd2c
--- /dev/null
+++ b/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
@@ -0,0 +1,80 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Interface::Email::Authz::RequireEncrypted;
+
+use strict;
+use warnings;
+
+use Role::Basic 'with';
+with 'RT::Interface::Email::Role';
+
+sub BeforeDecode {
+    my %args = (
+        Action      => undef,
+        Message     => undef,
+        CurrentUser => undef,
+        Ticket      => undef,
+        Queue       => undef,
+        @_,
+    );
+
+    my $encryption = $args{Message}->head->get('X-RT-Incoming-Encryption');
+    chomp $encryption;
+    return unless $encryption eq "Not encrypted";
+
+    RT::Interface::Email::Crypt::EmailErrorToSender(
+        %args,
+        Template  => 'Error: unencrypted message',
+        Arguments => { Message  => $args{'Message'} },
+    );
+    $RT::Logger->warning("rejected because the message is unencrypted");
+    FAILURE('rejected because the message is unencrypted');
+}
+
+1;
diff --git a/lib/RT/Interface/Email/Crypt.pm b/lib/RT/Interface/Email/Crypt.pm
index 7186192..e44ba9b 100644
--- a/lib/RT/Interface/Email/Crypt.pm
+++ b/lib/RT/Interface/Email/Crypt.pm
@@ -85,20 +85,9 @@ sub VerifyDecrypt {
         Entity => $args{'Message'},
     );
     if ( !@res ) {
-        if (RT->Config->Get('Crypt')->{'RejectOnUnencrypted'}) {
-            EmailErrorToSender(
-                %args,
-                Template  => 'Error: unencrypted message',
-                Arguments => { Message  => $args{'Message'} },
-            );
-            $RT::Logger->warning("rejected because the message is unencrypted with RejectOnUnencrypted enabled");
-            FAILURE('rejected because the message is unencrypted with RejectOnUnencrypted enabled');
-        }
-        else {
-            $args{'Message'}->head->replace(
-                'X-RT-Incoming-Encryption' => 'Not encrypted'
-            );
-        }
+        $args{'Message'}->head->replace(
+            'X-RT-Incoming-Encryption' => 'Not encrypted'
+        );
         return;
     }
 
diff --git a/t/mail/smime/reject_on_unencrypted.t b/t/mail/smime/reject_on_unencrypted.t
index ab62d83..60223ea 100644
--- a/t/mail/smime/reject_on_unencrypted.t
+++ b/t/mail/smime/reject_on_unencrypted.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef, actual_server => 1, config => 'Set( %Crypt, RejectOnUnencrypted => 1 );';
+use RT::Test::SMIME tests => undef, actual_server => 1, config => 'Set( @MailPlugins, "Authz::RequireEncrypted" );';
 my $test = 'RT::Test::SMIME';
 
 use IPC::Run3 'run3';

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

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

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

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

commit 51729534fad9e6a810540d87d4d85978c4313fd5
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 787d2f6..6de195b 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -188,9 +188,6 @@ sub Gateway {
     my $From = Encode::decode( "UTF-8", $head->get("From") );
     chomp $From if defined $From;
 
-    my $MessageId = Encode::decode( "UTF-8", $head->get('Message-ID') )
-        || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
-
     #Pull apart the subject line
     my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || '');
     chomp $Subject;
@@ -201,7 +198,6 @@ sub Gateway {
         if IsMachineGeneratedMail(
             Message   => $Message,
             Subject   => $Subject,
-            MessageId => $MessageId,
         );
 
     # Make all errors from here on out bounce back to $ErrorsTo
@@ -456,7 +452,6 @@ sub IsMachineGeneratedMail {
     my %args = (
         Message => undef,
         Subject => undef,
-        MessageId => undef,
         @_
     );
     my $head = $args{'Message'}->head;
@@ -477,7 +472,8 @@ sub IsMachineGeneratedMail {
 
     # Warn someone if it's a loop, before we drop it on the ground
     if ($IsALoop) {
-        $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
+        my $MessageId = Encode::decode( "UTF-8", $head->get('Message-ID') );
+        $RT::Logger->crit("RT Received mail ($MessageId) from itself.");
 
         #Should we mail it to RTOwner?
         if ( RT->Config->Get('LoopsToRTOwner') ) {

commit 1d1320bef51c74c4e5a091174d8875ad56af4768
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 6de195b..b0079fb 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -456,9 +456,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');
@@ -468,7 +466,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) {
@@ -523,48 +521,10 @@ sub CheckForLoops {
     return undef;
 }
 
-=head3 CheckForSuspiciousSender HEAD
-
-Takes a HEAD object of L<MIME::Head> class and returns true if sender
-is suspicious. Suspicious means mailer daemon.
-
-See also L</ParseSenderAddressFromHead>.
-
-=cut
-
-sub CheckForSuspiciousSender {
-    my $head = shift;
-
-    #if it's from a postmaster or mailer daemon, it's likely a bounce.
-
-    #TODO: better algorithms needed here - there is no standards for
-    #bounces, so it's very difficult to separate them from anything
-    #else.  At the other hand, the Return-To address is only ment to be
-    #used as an error channel, we might want to put up a separate
-    #Return-To address which is treated differently.
-
-    #TODO: search through the whole email and find the right Ticket ID.
-
-    my ( $From ) = ParseSenderAddressFromHead($head);
-
-    # If unparseable (non-ASCII), $From can come back undef
-    return undef if not defined $From;
-
-    if (   ( $From =~ /^mailer-daemon\@/i )
-        or ( $From =~ /^postmaster\@/i )
-        or ( $From eq "" ))
-    {
-        return (1);
-
-    }
-
-    return undef;
-}
-
 =head3 CheckForAutoGenerated HEAD
 
 Takes a HEAD object of L<MIME::Head> class and returns true if message is
-autogenerated. Checks C<Precedence>, C<Auto-Submitted>, and
+autogenerated. Checks C<Precedence>, RFC3834 C<Auto-Submitted>, and
 C<X-FC-Machinegenerated> fields of the head in tests.
 
 =cut
@@ -572,32 +532,29 @@ C<X-FC-Machinegenerated> fields of the head in tests.
 sub CheckForAutoGenerated {
     my $head = shift;
 
-    if (grep { /^(bulk|junk)/i } $head->get_all("Precedence")) {
-        return (1);
-    }
+    # Bounces, via return-path
+    my $ReturnPath = $head->get("Return-path") || "";
+    return 1 if $ReturnPath =~ /<>/;
+
+    # Bounces, via mailer-daemon or postmaster
+    my ( $From ) = ParseSenderAddressFromHead($head);
+    return 1 if defined $From and $From =~ /^mailer-daemon\@/i;
+    return 1 if defined $From and $From =~ /^postmaster\@/i;
+    return 1 if defined $From and $From eq "";
+
+    # Bulk or junk messages are auto-generated
+    return 1 if grep {/^(bulk|junk)/i} $head->get_all("Precedence");
 
     # Per RFC3834, any Auto-Submitted header which is not "no" means
     # it is auto-generated.
     my $AutoSubmitted = $head->get("Auto-Submitted") || "";
-    if ( length $AutoSubmitted and $AutoSubmitted ne "no" ) {
-        return (1);
-    }
+    return 1 if length $AutoSubmitted and $AutoSubmitted ne "no";
 
     # First Class mailer uses this as a clue.
     my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
-    if ( $FCJunk =~ /^true/i ) {
-        return (1);
-    }
-
-    return (0);
-}
+    return 1 if $FCJunk =~ /^true/i;
 
-
-sub CheckForBounce {
-    my $head = shift;
-
-    my $ReturnPath = $head->get("Return-path") || "";
-    return ( $ReturnPath =~ /<>/ );
+    return 0;
 }
 
 

commit 2f047f20fa0f262d9d1152a1b98b716adcd70c63
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 b0079fb..dc4eef0 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -144,11 +144,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,
         );
     }
 
@@ -323,8 +320,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
@@ -361,8 +358,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}" );
 }
 
 
@@ -646,6 +643,7 @@ sub MailError {
         MIMEObj     => undef,
         Attach      => undef,
         LogLevel    => 'crit',
+        FAILURE     => 0,
         @_
     );
 
@@ -689,6 +687,8 @@ sub MailError {
     }
 
     SendEmail( Entity => $entity, Bounce => 1 );
+
+    FAILURE( "$args{Subject}: $args{Explanation}" ) if $args{FAILURE};
 }
 
 =head2 SENDING EMAIL
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
index 105b356..e2e3504 100644
--- a/lib/RT/Interface/Email/Action/Defaults.pm
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -97,8 +97,8 @@ sub _HandleCreate {
     MailError(
         Subject     => "Ticket creation failed: $args{Subject}",
         Explanation => $ErrStr,
+        FAILURE     => 1,
     );
-    FAILURE("Ticket creation failed: $ErrStr", $args{Ticket} );
 }
 
 sub HandleComment {
@@ -123,12 +123,11 @@ sub _HandleEither {
     return _HandleCreate(@_) unless $args{TicketId};
 
     unless ( $args{Ticket}->Id ) {
-        my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
             Subject     => "Message not recorded: $args{Subject}",
-            Explanation => $error,
+            Explanation => "Could not find a ticket with id " . $args{TicketId},
+            FAILURE     => 1,
         );
-        FAILURE( $error );
     }
 
     my $action = ucfirst $args{Action};
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
index daaa3dd..0ffa936 100644
--- a/lib/RT/Interface/Email/Action/Resolve.pm
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -63,12 +63,11 @@ sub HandleResolve {
     );
 
     unless ( $args{Ticket}->Id ) {
-        my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
             Subject     => "Message not recorded: $args{Subject}",
-            Explanation => $error,
+            Explanation => "Could not find a ticket with id " . $args{TicketId},
+            FAILURE     => 1,
         );
-        FAILURE( $error );
     }
 
     my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
@@ -83,8 +82,8 @@ sub HandleResolve {
     MailError(
         Subject     => "Ticket not resolved",
         Explanation => $msg,
+        FAILURE     => 1,
     );
-    FAILURE( "Ticket not resolved, by email From: $From" );
 }
 
 1;
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
index 60b9b15..4c599ca 100644
--- a/lib/RT/Interface/Email/Action/Take.pm
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -63,12 +63,11 @@ sub HandleTake {
     );
 
     unless ( $args{Ticket}->Id ) {
-        my $error = "Could not find a ticket with id " . $args{TicketId};
         MailError(
             Subject     => "Message not recorded: $args{Subject}",
-            Explanation => $error,
+            Explanation => "Could not find a ticket with id " . $args{TicketId},
+            FAILURE     => 1,
         );
-        FAILURE( $error );
     }
 
     my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
@@ -79,8 +78,8 @@ sub HandleTake {
     MailError(
         Subject     => "Ticket not taken",
         Explanation => $msg,
+        FAILURE     => 1,
     );
-    FAILURE( "Ticket not taken, by email From: $From" );
 }
 
 1;
diff --git a/lib/RT/Interface/Email/Authz/Default.pm b/lib/RT/Interface/Email/Authz/Default.pm
index 5815672..a364bb2 100644
--- a/lib/RT/Interface/Email/Authz/Default.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -128,8 +128,8 @@ EOT
     MailError(
         Subject     => "Permission Denied",
         Explanation => $msg,
+        FAILURE     => 1,
     );
-    FAILURE( $msg );
 }
 
 1;

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

    Move ACL checking for Take and Resolve into their own plugins

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

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

    Update POD for new methods and functionality

diff --git a/docs/extending/mail_plugins.pod b/docs/extending/mail_plugins.pod
index ea43e2e..ce29d4f 100644
--- a/docs/extending/mail_plugins.pod
+++ b/docs/extending/mail_plugins.pod
@@ -1,67 +1,250 @@
 =head1 MAIL PLUGINS
 
 By default, the mail gateway will accept mail from anyone. However,
-there are situations in which you will want to authenticate users
-before allowing them to communicate with the system. You can do this
-via a plug-in mechanism in the RT configuration.
-
-You can set the array C<@MailPlugins> to be a list of plugins. The
-default plugin, if this is not given, is C<Auth::MailFrom> - that is,
-authentication of the person is done based on the C<From> header of the
-email. If you have additional filters or authentication mechanisms, you
-can list them here and they will be called in order:
-
-    Set( @MailPlugins =>
-        "RT::Extension::SpamAssassin",
-        "Auth::Crypt",
-        "Auth::MailFrom",
+there are situations in which you will want to authenticate users before
+allowing them to communicate with the system.  You can do this via a
+plug-in mechanism in the RT configuration.
+
+You can set the array C<@MailPlugins> to be a list of plugins. For
+example, to allow the mailgate to specify a 'take' action, you can use
+the bundled L<RT::Interface::Email::Actions::Take>:
+
+    Set( @MailPlugins,
+        "Actions::Take",
     );
 
-See the documentation for any additional plugins you have.
+Anonymous subroutine references found in C<@MailPlugins> are treated
+like L</GetCurrentUser> methods.
+
+
+=head2 Core plugins
+
+A small number of plugins are included with core RT, but not enabled by
+default:
+
+=over
+
+=item L<RT::Interface::Email::Actions::Take>
+
+Allows the mailgate to specify C<--action take-comment>, for instance,
+which would take the ticket before commenting.  This action is somewhat
+"unsafe," which is why it is not enabled by default.  It can also often
+be accomplished via a scrip instead.
+
+=item L<RT::Interface::Email::Actions::Resolve>
+
+Allows the mailgate to specify C<--action correspond-resolve>, for
+instance, which would correspond, then resolve the ticket.  This action
+is somewhat "unsafe," which is why it is not enabled by default.  It can
+also often be accomplished via a scrip instead.
+
+=item L<RT::Interface::Email::Authz::RequireEncrypted>
+
+Forces that all incoming mail be encrypted to be accepted.
+
+=back
 
 You may also put Perl subroutines into the C<@MailPlugins> array, if
 they behave as described below.
 
 =head1 WRITING PLUGINS
 
-What's actually going on in the above is that C<@MailPlugins> is a
-list of Perl modules; RT prepends C<RT::Interface::Email::> to the name,
-to form a package name, and then C<use>'s this module. The module is
-expected to provide a C<GetCurrentUser> subroutine, which takes a hash of
-several parameters:
+C<@MailPlugins> is a list of Perl modules; RT prepends
+C<RT::Interface::Email::> to the name to form a package name, and then
+C<use>'s this module.  The module should implement
+L<RT::Interface::Email::Role>.
+
+=head2 Plugin methods
+
+L<RT::Interface::Email::Role> defines a number of functions which are
+useful for immediately aborting processing.  They include
+L<RT::Interface::Email::Role/SUCCESS>,
+L<RT::Interface::Email::Role/FAILURE>, and
+L<RT::Interface::Email::Role/TMPFAIL>; read their descriptions for
+information on how to immediately abort processing from mail plugins.
+
+=head2 Plugin hooks
+
+Mail plugins are expected to provide one or more of the following
+methods:
 
-=over 4
+=head3 BeforeDecrypt
+
+Called before the message is decoded or decrypted.  Its return value is
+ignored; it is passed the following parameters:
+
+=over
 
 =item Message
 
-A C<MIME::Entity> object representing the email
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
 
-=item CurrentUser
+=item RawMessage
 
-An C<RT::CurrentUser> object
+A reference to the string containing the original message.  This should
+not be modified.
 
-=item AuthStat
+=item Queue
 
-The authentication level returned from the previous plugin.
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
 
-=item Ticket [OPTIONAL]
+=item Actions
 
-The ticket under discussion
+An array reference of actions to perform; the C<--action> argument which
+was passed to L<rt-mailgate>.  This may be modified.
+
+=back
+
+
+=head3 BeforeDecode
+
+Called after the message has been decrypted and verified, but before the
+bodies have been decoded of their content transfer encoding.  Its return
+value is ignored; it is passed the following parameters:
+
+=over
+
+=item Message
+
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
+
+=item RawMessage
+
+A reference to the string containing the original message.  This should
+not be modified.
+
+=item Queue
+
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
+
+=item Actions
+
+An array reference of actions to perform; the C<--action> argument which
+was passed to L<rt-mailgate>.  This may be modified.
+
+=back
+
+
+
+=head3 GetCurrentUser
+
+This method is called in order on the mail plugins that define it.  The
+first method to return a L<RT::CurrentUser> value shortcuts all other
+plugins.  It is passed:
+
+=over
+
+=item Message
 
-=item Queue [OPTIONAL]
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
 
-If we don't already have a ticket id, we need to know which queue we're talking about
+=item RawMessage
+
+A reference to the string containing the original message.  This should
+not be modified.
+
+=item Ticket
+
+A L<RT::Ticket>, the ticket (if any) that has been extracted from the
+subject.  If there was no ticket id, this value will be a L<RT::Ticket>
+object with no C<id>.
+
+=item Queue
+
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
+
+=back
+
+
+=head3 CheckACL
+
+Called to determine authorization -- namely, can the current user
+complete the action in question?  While RT's standard permission
+controls apply, this allows a better error message, or more limited
+restrictions on the email gateway.
+
+Only the I<first> action (if there are more than one defined) is
+checked, as the process of completing the first action might affect the
+later actions; consider the case of C<take-correspond>, where the
+C<correspond> action might only be avilable to owners.
+
+Each plugin defining this method is called in turn; as soon as one
+plugin returns true, the rest are short-circuited.  Arguments include:
+
+=over
+
+=item Message
+
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
+
+=item CurrentUser
+
+A L<RT::CurrentUser> object representing the authenticated user.
 
 =item Action
 
-The action being performed. At the moment, it's one of "comment" or "correspond"
+A string representing the action to be undertaken.
+
+=item Ticket
+
+A L<RT::Ticket>, the ticket (if any) that has been extracted from the
+subject.  If there was no ticket id, this value will be a L<RT::Ticket>
+object with no C<id>.
+
+=item Queue
+
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
+
+=back
+
+
+=head3 HandleI<Action>
+
+For any given action I<foo>, the presence of a subroutine called
+C<HandleFoo> signals the ability of the mailgate to handle that action.
+The first plugin in to define the method is called, and its return value
+ignored.  It is passed:
+
+=over
+
+=item Message
+
+A L<MIME::Entity> object representing the mail.  This may be modified by
+the plugin.
+
+=item Subject
+
+A string, the original C<Subject> header of the message before it was
+modified to extract the ticket id.
+
+=item CurrentUser
+
+A L<RT::CurrentUser> object representing the authenticated user.
+
+=item Ticket
+
+A L<RT::Ticket>, the ticket (if any) that has been extracted from the
+subject.  If there was no ticket id, this value will be a L<RT::Ticket>
+object with no C<id>.
+
+=item TicketId
+
+The value id that was extracted from the subject; this allows a
+non-existant ticket id to be differentiated from no subject id, as both
+will present as having an unloaded C<Ticket> argument.
+
+=item Queue
+
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
 
 =back
 
-It returns two values, the new C<RT::CurrentUser> object, and the new
-authentication level. The authentication level can be zero, not allowed
-to communicate with RT at all, (a "permission denied" error is mailed to
-the correspondent) or one, which is the normal mode of operation.
-Additionally, if C<-1> is returned, then the processing of the plug-ins
-stops immediately and the message is ignored.
+=head1 SEE ALSO
+
+L<RT::Interface::Email::Role>
 
+=cut
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index dc4eef0..025bf2c 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -67,7 +67,7 @@ use 5.010;
 
 =head1 NAME
 
-  RT::Interface::Email - helper functions for parsing email sent to RT
+  RT::Interface::Email - helper functions for parsing and sending email
 
 =head1 METHODS
 
@@ -78,32 +78,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
 
@@ -248,6 +257,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,
@@ -290,7 +307,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
 
@@ -324,13 +355,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
 
@@ -363,6 +400,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,
@@ -385,14 +429,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
 
@@ -414,9 +458,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
 
@@ -432,16 +475,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
 
@@ -497,9 +533,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
 
@@ -555,16 +590,17 @@ sub CheckForAutoGenerated {
 }
 
 
-=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
 
@@ -576,7 +612,7 @@ sub ExtractTicketId {
     return ParseTicketId( $subject );
 }
 
-=head2 ParseTicketId
+=head3 ParseTicketId
 
 Takes a string and searches for [subjecttag #id]
 
@@ -614,21 +650,35 @@ Sends an error message. Takes a param hash:
 
 =over 4
 
-=item From - sender's address, by default is 'CorrespondAddress';
+=item From
+
+Sender's address, defaults to L<RT_Config/$CorrespondAddress>;
+
+=item To
+
+Recipient, defaults to L<RT_Config/$OwnerEmail>;
+
+=item Subject
+
+Subject of the message, defaults to C<There has been an error>;
+
+=item Explanation
+
+Main content of the error, default value is C<Unexplained error>;
 
-=item To - recipient, by default is 'OwnerEmail';
+=item MIMEObj
 
-=item Subject - subject of the message, default is 'There has been an error';
+Optional L<MIME::Entity> that is attached to the error mail.
+Additionally, the C<In-Reply-To> header will point to this message.
 
-=item Explanation - main content of the error, default value is 'Unexplained error';
+=item Attach
 
-=item MIMEObj - optional MIME entity that's attached to the error mail, as well we
-add 'In-Reply-To' field to the error that points to this message.
+Optional text that attached to the error as a C<message/rfc822> part.
 
-=item Attach - optional text that attached to the error as 'message/rfc822' part.
+=item LogLevel
 
-=item LogLevel - log level under which we should write the subject and
-explanation message into the log, by default we log it as critical.
+Log level the subject and explanation is written to the log; defaults to
+C<critical>.
 
 =back
 
diff --git a/lib/RT/Interface/Email/Action/Defaults.pm b/lib/RT/Interface/Email/Action/Defaults.pm
index e2e3504..c51101e 100644
--- a/lib/RT/Interface/Email/Action/Defaults.pm
+++ b/lib/RT/Interface/Email/Action/Defaults.pm
@@ -56,6 +56,18 @@ with 'RT::Interface::Email::Role';
 
 use RT::Interface::Email;
 
+=head1 NAME
+
+RT::Interface::Email::Action::Defaults - RT's core email integration
+
+=head1 SYNOPSIS
+
+This module B<should not> be explicitly included in
+L<RT_Config/@MailPlugins>; RT includes it automatically.  It provides
+the C<comment> and C<correspond> actions.
+
+=cut
+
 sub _HandleCreate {
     my %args = (
         Subject     => undef,
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
index b0fdaf7..bc0058d 100644
--- a/lib/RT/Interface/Email/Action/Resolve.pm
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -54,6 +54,21 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Action::Resolve - Resolve tickets via the mail gateway
+
+=head1 SYNOPSIS
+
+This plugin, if placed in L<RT_Config/@MailPlugins>, allows the mail
+gateway to specify a resolve action:
+
+    | rt-mailgate --action correspond-resolve --queue General --url http://localhost/
+
+This can alternately (and more flexibly) be accomplished with a Scrip.
+
+=cut
+
 sub CheckACL {
     my %args = (
         Message     => undef,
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
index e18d439..1813a94 100644
--- a/lib/RT/Interface/Email/Action/Take.pm
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -54,6 +54,21 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Action::Take - Take tickets via the mail gateway
+
+=head1 SYNOPSIS
+
+This plugin, if placed in L<RT_Config/@MailPlugins>, allows the mail
+gateway to specify a take action:
+
+    | rt-mailgate --action take-correspond --queue General --url http://localhost/
+
+This can alternately (and more flexibly) be accomplished with a Scrip.
+
+=cut
+
 sub CheckACL {
     my %args = (
         Message     => undef,
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index f92bfe1..89033c8 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -54,6 +54,23 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Auth::MailFrom - The default mail gateway authenticator
+
+=head1 SYNOPSIS
+
+This is the default authentication plugin for RT's email gateway; no no
+other authentication plugin is found in L<RT_Config/@MailPlugins>, RT
+will default to this one.
+
+This plugin reads the first address found in the C<Reply-To>, C<From>,
+and C<Sender> headers, and loads or creates the user.  It performs no
+checking of the identity of the user, and trusts the headers of the
+incoming email.
+
+=cut
+
 sub GetCurrentUser {
     my %args = (
         Message => undef,
diff --git a/lib/RT/Interface/Email/Authz/Default.pm b/lib/RT/Interface/Email/Authz/Default.pm
index 545e23f..9a96a0a 100644
--- a/lib/RT/Interface/Email/Authz/Default.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -54,6 +54,20 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Authz::Default - RT's core authorization for the mail gateway
+
+=head1 SYNOPSIS
+
+This module B<should not> be explicitly included in
+L<RT_Config/@MailPlugins>; RT includes it automatically.  It provides
+authorization checks for the core the C<comment> and C<correspond>
+actions, via examining RT's standard rights for C<CommentOnTicket>,
+C<ReplyToTicket>, or C<CreateTicket> as necessary.
+
+=cut
+
 sub CheckACL {
     my %args = (
         Message     => undef,
diff --git a/lib/RT/Interface/Email/Authz/RequireEncrypted.pm b/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
index b96cd2c..6dc159d 100644
--- a/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
+++ b/lib/RT/Interface/Email/Authz/RequireEncrypted.pm
@@ -54,6 +54,18 @@ use warnings;
 use Role::Basic 'with';
 with 'RT::Interface::Email::Role';
 
+=head1 NAME
+
+RT::Interface::Email::Authz::RequireEncrypted - Require that incoming email be encrypted
+
+=head1 SYNOPSIS
+
+This plugin allows restricting incoming emails to those which were
+encrypted.  Plaintext emails are rejected, with a reply sent to the
+originator using the C<Error: unencrypted message> template.
+
+=cut
+
 sub BeforeDecode {
     my %args = (
         Action      => undef,
diff --git a/lib/RT/Interface/Email/Crypt.pm b/lib/RT/Interface/Email/Crypt.pm
index fd37e5d..86502f9 100644
--- a/lib/RT/Interface/Email/Crypt.pm
+++ b/lib/RT/Interface/Email/Crypt.pm
@@ -55,6 +55,10 @@ use warnings;
 
 RT::Interface::Email::Crypt - decrypting and verifying protected emails
 
+=head1 SEE ALSO
+
+See L<RT::Crypt>.
+
 =cut
 
 use RT::Crypt;
diff --git a/lib/RT/Interface/Email/Role.pm b/lib/RT/Interface/Email/Role.pm
index 2b0519d..f30ad97 100644
--- a/lib/RT/Interface/Email/Role.pm
+++ b/lib/RT/Interface/Email/Role.pm
@@ -55,6 +55,87 @@ use Role::Basic;
 
 use RT::Interface::Email;
 
+=head1 NAME
+
+RT::Interface::Email::Role - Role for mail plugins
+
+=head1 SYNOPSIS
+
+    package RT::Interface::Email::Action::Something;
+
+    use Role::Basic 'with';
+    with 'RT::Interface::Email::Role';
+
+    sub CheckACL { ... }
+    sub HandleSomething { ... }
+
+=head1 DESCRIPTION
+
+Provides a means to affect the handling of RT's mail gateway.  Mail
+plugins (which appear in L<RT_Config/@MailPlugins> should implement this
+role.
+
+See F<docs/extending/mail_plugins.pod> for a list of hook points which
+plugins can implement.
+
+=head1 METHODS
+
+=head2 TMPFAIL
+
+This should be called for configuration errors.  It results in a
+temporary failure code to the MTA, ensuring that the message is not
+lost, and will be retried later.
+
+This function should be passed the warning message to log with the
+temporary failure.  Does not return.
+
+
+=head2 FAILURE
+
+This should be used upon rejection of a message.  It will B<not> be
+retried by the MTA; as such, it should be used sparingly, and in
+conjunction with L</MailError> such that the sender is aware of the
+failure.
+
+The function should be passed a message to return to the mailgate.  Does
+not return.
+
+
+=head2 SUCCESS
+
+The message was successfully parsed.  The function takes an optional
+L<RT::Ticket> object to return to the mailgate. Does not return.
+
+
+=head2 MailError
+
+Sends an error concerning the email, or the processing thereof.  Takes
+the following arguments:
+
+=over
+
+=item To
+
+Only necessary in L</BeforeDecode> and L<BeforeDecrypt> hooks, where it
+defaults to L<RT_Config/OwnerEmail>; otherwise, defaults to the
+originator of the message.
+
+=item Subject
+
+Subject of the email
+
+=item Explanation
+
+The body of the email
+
+=item FAILURE
+
+If passed a true value, will call L</FAILURE> after sending the message.
+
+=back
+
+=cut
+
 sub TMPFAIL { RT::Interface::Email::TMPFAIL(@_) }
 sub FAILURE { RT::Interface::Email::FAILURE(@_) }
 sub SUCCESS { RT::Interface::Email::SUCCESS(@_) }

commit 044fe16dc970ae6d51a1fa1ced92b102cd695fa3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Feb 2 22:59:45 2012 +0400

    simple test for multiple reply-to addresses

diff --git a/t/mail/multiple-reply-to.t b/t/mail/multiple-reply-to.t
new file mode 100644
index 0000000..2afa96c
--- /dev/null
+++ b/t/mail/multiple-reply-to.t
@@ -0,0 +1,51 @@
+use strict;
+use warnings;
+
+use RT::Test tests => 7;
+
+diag "grant everybody with CreateTicket right";
+{
+    ok( RT::Test->set_rights(
+        { Principal => 'Everyone', Right => [qw(CreateTicket)], },
+        { Principal => 'Requestor', Right => [qw(ReplyToTicket)], },
+    ), "Granted rights");
+}
+
+{
+    my $text = <<EOF;
+From: user\@example.com
+Subject: test
+
+Blah!
+Foob!
+EOF
+    my ($status, $id) = RT::Test->send_via_mailgate($text);
+    is ($status >> 8, 0, "The mail gateway exited normally");
+    ok ($id, "ticket created");
+
+    $text = <<EOF;
+From: user\@example.com
+Subject: [@{[RT->Config->Get('rtname')]} #$id] test
+
+Blah!
+Foob!
+EOF
+    ($status, my $tid) = RT::Test->send_via_mailgate($text);
+    is ($status >> 8, 0, "The mail gateway exited normally");
+    is ($tid, $id, "ticket updated");
+
+    $text = <<EOF;
+From: somebody\@example.com
+Reply-To: boo\@example.com, user\@example.com
+Subject: [@{[RT->Config->Get('rtname')]} #$id] test
+
+Blah!
+Foob!
+EOF
+    ($status, $tid) = RT::Test->send_via_mailgate($text);
+    is ($status >> 8, 0, "The mail gateway exited normally");
+    is ($tid, $id, "ticket updated");
+}
+
+
+1;

commit b4b94b3f5b37eff8f8024b8ce3970836bc97ea8d
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 7 00:01:11 2014 +0800

    take into account multiple sender's addresses
    
    use first address that has rights, if none has rights then do what we did
    before.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 025bf2c..160e9b6 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -222,7 +222,7 @@ sub Gateway {
     TMPFAIL("RT couldn't find the queue: " . $args{'queue'})
         unless $SystemTicket->id || $SystemQueueObj->id;
 
-    my $CurrentUser = GetCurrentUser(
+    my @CurrentUsers = GetCurrentUser(
         Message       => $Message,
         RawMessageRef => \$args{message},
         Ticket        => $SystemTicket,
@@ -231,10 +231,10 @@ sub Gateway {
 
     # We only care about ACLs on the _first_ action, as later actions
     # may have gotten rights by the time they happen.
-    CheckACL(
+    my $CurrentUser = CheckACL(
         Action        => $actions[0],
         Message       => $Message,
-        CurrentUser   => $CurrentUser,
+        CurrentUser  => \@CurrentUsers,
         Ticket        => $SystemTicket,
         Queue         => $SystemQueueObj,
     );
@@ -311,15 +311,15 @@ sub Plugins {
 
 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.
+tried one at a time, and stops after the first to return at least one 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
+address, and loads or creates users accordingly; see
 L<RT::Interface::Email::Auth::MailFrom>.
 
-Returns the current user; on failure of any plugin to do so, stops
+Returns a list of possible CurrentUsers; on failure of any plugin to do so, stops
 processing with a permanent failure and sends a generic "Permission
 Denied" mail to the user.
 
@@ -336,13 +336,13 @@ sub GetCurrentUser {
 
     # Since this needs loading, no matter what
     for my $Code ( Plugins(Code => 1, Method => "GetCurrentUser") ) {
-        my $CurrentUser = $Code->(
+        my @CurrentUsers = $Code->(
             Message       => $args{Message},
             RawMessageRef => $args{RawMessageRef},
             Ticket        => $args{Ticket},
             Queue         => $args{Queue},
         );
-        return $CurrentUser if $CurrentUser and $CurrentUser->id;
+        return @CurrentUsers if @CurrentUsers;
     }
 
     # None of the GetCurrentUser plugins found a user.  This is
@@ -355,9 +355,9 @@ sub GetCurrentUser {
     );
 }
 
-=head3 CheckACL Action => C<action>, CurrentUser => C<user>, Ticket => C<ticket>, Queue => C<queue>
+=head3 CheckACL Action => C<action>, CurrentUser => [C<user>] or C<user>, Ticket => C<ticket>, Queue => C<queue>
 
-Checks that the currentuser can perform a particular action.  While RT's
+Checks that the currentusers 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.
 
@@ -369,6 +369,8 @@ 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.
 
+Returns the first user that passed the check.
+
 =cut
 
 sub CheckACL {
@@ -382,13 +384,14 @@ sub CheckACL {
     );
 
     for my $Code ( Plugins( Method => "CheckACL" ) ) {
-        return if $Code->(
-            Message       => $args{Message},
-            CurrentUser   => $args{CurrentUser},
-            Action        => $args{Action},
-            Ticket        => $args{Ticket},
-            Queue         => $args{Queue},
+        my $CurrentUser = $Code->(
+            Message     => $args{Message},
+            CurrentUser => $args{CurrentUser},
+            Action      => $args{Action},
+            Ticket      => $args{Ticket},
+            Queue       => $args{Queue},
         );
+        return $CurrentUser if $CurrentUser;
     }
 
     # Nobody said yes, and nobody said FAILURE; fail closed
@@ -441,19 +444,39 @@ investigate the parse failure.
 =cut
 
 sub ParseSenderAddressFromHead {
+    my ( $list, @errors ) = ParseSenderAddressesFromHead(@_);
+    if ( $list ) {
+        return ( $list->[0]->address, $list->[0]->phrase, @errors );
+    }
+    else {
+        return ( undef, undef, @errors );
+    }
+}
+
+=head2 ParseSenderAddressesFromHead HEAD
+
+Takes a MIME::Header object. Returns ([list of addreses], errors)
+where the addresses are found in C<Reply-To>, C<From> and C<Sender>.
+
+=cut
+
+sub ParseSenderAddressesFromHead {
     my $head = shift;
     my @errors;  # Accumulate any errors
 
+    my @addr;
     foreach my $header ( 'Reply-To', 'From', 'Sender' ) {
         my $addr_line = Encode::decode( "UTF-8", $head->get($header) ) || next;
-        my ($addr) = RT::EmailParser->ParseEmailAddress( $addr_line );
-        return ($addr->address, $addr->phrase, @errors) if $addr;
+        push @addr, RT::EmailParser->ParseEmailAddress( $addr_line );
 
-        chomp $addr_line;
-        push @errors, "$header: $addr_line";
+        unless ( @addr ) {
+            chomp $addr_line;
+            push @errors, "$header: $addr_line";
+        }
     }
 
-    return (undef, undef, @errors);
+    return (\@addr, @errors) if @addr;
+    return (undef, @errors);
 }
 
 =head3 ParseErrorsToAddressFromHead HEAD
diff --git a/lib/RT/Interface/Email/Action/Resolve.pm b/lib/RT/Interface/Email/Action/Resolve.pm
index bc0058d..8c84527 100644
--- a/lib/RT/Interface/Email/Action/Resolve.pm
+++ b/lib/RT/Interface/Email/Action/Resolve.pm
@@ -89,15 +89,17 @@ sub CheckACL {
         );
     }
 
-    my $principal = $args{CurrentUser}->PrincipalObj;
-    return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ModifyTicket' );
-
-    my $email = $args{CurrentUser}->UserObj->EmailAddress;
+    my @emails;
+    for my $CurrentUser ( ref $args{CurrentUser} eq 'ARRAY' ? @{ $args{CurrentUser} } : $args{CurrentUser} ) {
+        my $principal = $CurrentUser->PrincipalObj;
+        return $CurrentUser if $principal->HasRight( Object => $args{'Ticket'}, Right => 'ModifyTicket' );
+        push @emails, $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",
+        Explanation => (@emails == 1 ? "@emails has" : join(", ", @emails) . " have") . " no right to own ticket $tid in queue $qname",
         FAILURE     => 1,
     );
 }
diff --git a/lib/RT/Interface/Email/Action/Take.pm b/lib/RT/Interface/Email/Action/Take.pm
index 1813a94..3e49827 100644
--- a/lib/RT/Interface/Email/Action/Take.pm
+++ b/lib/RT/Interface/Email/Action/Take.pm
@@ -89,15 +89,18 @@ sub CheckACL {
         );
     }
 
-    my $principal = $args{CurrentUser}->PrincipalObj;
-    return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' );
+    my @emails;
+    for my $CurrentUser ( ref $args{CurrentUser} eq 'ARRAY' ? @{ $args{CurrentUser} } : $args{CurrentUser} ) {
+        my $principal = $CurrentUser->PrincipalObj;
+        return $CurrentUser if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'OwnTicket' );
+        push @emails, $CurrentUser->UserObj->EmailAddress;
+    }
 
-    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",
+        Explanation => (@emails == 1 ? "@emails has" : join(", ", @emails) . " have") . " no right to own ticket $tid in queue $qname",
         FAILURE     => 1,
     );
 }
diff --git a/lib/RT/Interface/Email/Auth/MailFrom.pm b/lib/RT/Interface/Email/Auth/MailFrom.pm
index 89033c8..282cba1 100644
--- a/lib/RT/Interface/Email/Auth/MailFrom.pm
+++ b/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -64,10 +64,9 @@ 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.
+This plugin reads addresses found in the C<Reply-To>, C<From>, and C<Sender>
+headers, and loads or creates users accordingly. It performs no checking of
+the identity of the users, and trusts the headers of the incoming email.
 
 =cut
 
@@ -78,35 +77,35 @@ sub GetCurrentUser {
     );
 
     # 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 ) {
+    my ($addresses, @errors) = RT::Interface::Email::ParseSenderAddressesFromHead( $args{'Message'}->head );
+    $RT::Logger->warning("Failed to parse ".join(', ', @errors)) if @errors;
+    unless ( $addresses ) {
         $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 @CurrentUsers;
+    foreach my $addr ( @$addresses ) {
+        $RT::Logger->debug("Testing $addr as sender");
+
+        my $CurrentUser = RT::CurrentUser->new;
+        $CurrentUser->LoadByEmail( $addr->address );
+        $CurrentUser->LoadByName( $addr->address ) unless $CurrentUser->Id;
+        unless ( $CurrentUser->Id ) {
+            my $user = RT::User->new( RT->SystemUser );
+            $user->LoadOrCreateByEmail(
+                RealName     => $addr->phrase,
+                EmailAddress => $addr->address,
+                Comments     => 'Autocreated on ticket submission',
+            );
+
+            $CurrentUser = RT::CurrentUser->new;
+            $CurrentUser->Load( $user->id );
+        }
+        push @CurrentUsers, $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;
+    return @CurrentUsers;
 }
 
 1;
diff --git a/lib/RT/Interface/Email/Authz/Default.pm b/lib/RT/Interface/Email/Authz/Default.pm
index 9a96a0a..2e558ff 100644
--- a/lib/RT/Interface/Email/Authz/Default.pm
+++ b/lib/RT/Interface/Email/Authz/Default.pm
@@ -78,21 +78,45 @@ sub CheckACL {
         @_,
     );
 
-    my $principal = $args{CurrentUser}->PrincipalObj;
-    my $email     = $args{CurrentUser}->UserObj->EmailAddress;
-    my $qname     = $args{'Queue'}->Name;
+    my @emails;
+    for my $CurrentUser ( ref $args{CurrentUser} eq 'ARRAY' ? @{ $args{CurrentUser} } : $args{CurrentUser} ) {
+        my $principal = $CurrentUser->PrincipalObj;
+        push @emails, $CurrentUser->UserObj->EmailAddress;
+
+        if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
+            if ( $args{'Action'} =~ /^comment$/i ) {
+                return $CurrentUser if $principal->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' );
+            }
+            elsif ( $args{'Action'} =~ /^correspond$/i ) {
+                return $CurrentUser if $principal->HasRight( Object => $args{'Ticket'}, Right => 'ReplyToTicket' );
+            }
+            else {
+                $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
+                return;
+            }
+        }
+
+        # We're creating a ticket
+        elsif ( $args{'Action'} =~ /^(comment|correspond)$/i ) {
+            return $CurrentUser if $principal->HasRight( Object => $args{'Queue'}, Right => 'CreateTicket' );
+        }
+        else {
+            $RT::Logger->warning( "Action '" . ( $args{'Action'} || '' ) . "' is unknown with no ticket" );
+            return;
+        }
+    }
 
     my $msg;
+    my $email = join ", ", @emails;
+    my $qname = $args{'Queue'}->Name;
     if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
-        my $tid   = $args{'Ticket'}->id;
+        my $tid = $args{'Ticket'}->id;
 
         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";
+            $msg = (@emails ? "$email has" : "$email have") . " no right to comment on ticket $tid in queue $qname";
         }
         elsif ( $args{'Action'} =~ /^correspond$/i ) {
-            return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right  => 'ReplyToTicket' );
-            $msg = "$email has no right to reply to ticket $tid in queue $qname";
+            $msg = (@emails ? "$email has" : "$email have") . " no right to reply to ticket $tid in queue $qname";
 
             # Also notify the owner
             MailError(
@@ -104,16 +128,10 @@ might need to grant 'Everyone' the ReplyToTicket right.
 EOT
             );
         }
-        else {
-            $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown");
-            return;
-        }
     }
-
     # We're creating a ticket
     elsif ( $args{'Action'} =~ /^(comment|correspond)$/i ) {
-        return 1 if $principal->HasRight( Object => $args{'Queue'}, Right  => 'CreateTicket' );
-        $msg = "$email has no right to create tickets in queue $qname";
+        $msg = (@emails ? "$email has" : "$email have") . " no right to create tickets in queue $qname";
 
         # Also notify the owner
         MailError(
@@ -125,11 +143,6 @@ might need to grant 'Everyone' the CreateTicket right.
 EOT
         );
     }
-    else {
-        $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown with no ticket");
-        return;
-    }
-
 
     MailError(
         Subject     => "Permission Denied",

commit c179f586d0d2c90590d4953fe307a255949fe598
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Jun 4 22:09:14 2015 +0800

    Email::Address 1.900+ has a fix of uninitialized warning
    
    the warning could be trigged by empty phrases of email addresses in
    t/data/emails/crashes-file-based-parser:
    
        Sender: <noc at rt3.mx.example.com> (Network Operation Center)
    
    which is used in t/mail/sendmail.t and t/mail/sendmail-plaintext.t

diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
index 7ce5d29..94931fd 100644
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -211,7 +211,7 @@ Devel::StackTrace 1.19
 Digest::base
 Digest::MD5 2.27
 Digest::SHA
-Email::Address 1.897
+Email::Address 1.900
 Email::Address::List 0.02
 Encode 2.64
 Errno

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


More information about the rt-commit mailing list