[Rt-commit] rt branch, 4.4/multiple-reply-to, repushed
Alex Vandiver
alexmv at bestpractical.com
Thu May 7 14:39:50 EDT 2015
The branch 4.4/multiple-reply-to was deleted and repushed:
was 395e0ca730b634f50c8227576fa1620aafbb8eb2
now b78d166c931ea0f39ccc5f5f7d4ff374dc81462f
1: 15a0c3b ! 1: e0497a2 RT::Interface::Email doesn't need to be an Exporter
@@ -8,8 +8,8 @@
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@
- The support for C<jsmin> (via the C<$JSMinPath> configuration) has been
- removed in favor of a built-in solution.
+ Estimated, and Time Left will no longer be copied. This simplifies time
+ reporting.
+=item *
+
@@ -23,8 +23,8 @@
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@
- use Mail::Mailer ();
- use Text::ParseWords qw/shellwords/;
+ use RT::Util 'safe_run_child';
+ use File::Spec;
-BEGIN {
- use base 'Exporter';
2: 9b135a2 ! 2: e3399f0 Reorder functions to more obviously split sending and receiving mail
@@ -9,9 +9,9 @@
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@
- use UNIVERSAL::require;
- use Mail::Mailer ();
use Text::ParseWords qw/shellwords/;
+ use RT::Util 'safe_run_child';
+ use File::Spec;
+use MIME::Words ();
=head1 NAME
@@ -32,7 +32,7 @@
- my $head = shift;
-
- # If this instance of RT sent it our, we don't want to take it in
-- my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
+- 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;
@@ -101,14 +101,18 @@
-=head2 CheckForAutoGenerated HEAD
--Takes a HEAD object of L<MIME::Head> class and returns true if message
--is autogenerated. Checks 'Precedence' and 'X-FC-Machinegenerated'
--fields of the head in tests.
+-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 = (
@@ -119,17 +123,18 @@
+ %$argsref
+ );
-- my $Precedence = $head->get("Precedence") || "";
-- if ( $Precedence =~ /^(bulk|junk)/i ) {
-- return (1);
-- }
-+ my $SystemTicket;
-+ my $Right;
-
- # Per RFC3834, any Auto-Submitted header which is not "no" means
- # it is auto-generated.
- my $AutoSubmitted = $head->get("Auto-Submitted") || "";
- if ( length $AutoSubmitted and $AutoSubmitted ne "no" ) {
+- return (1);
+- }
++ 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'} );
@@ -144,17 +149,18 @@
+ );
}
-- # First Class mailer uses this as a clue.
-- my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
-- if ( $FCJunk =~ /^true/i ) {
-- return (1);
+- 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(
@@ -162,24 +168,25 @@
+ 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"
+ );
- }
-
-- return (0);
--}
++ }
+
+ 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'} );
--sub CheckForBounce {
-- my $head = shift;
+-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
@@ -195,11 +202,10 @@
+ Actions => \@actions,
+ );
-- my $ReturnPath = $head->get("Return-path") || "";
-- return ( $ReturnPath =~ /<>/ );
--}
+-=over 4
+ $skip_plugin{ $class }++;
+-=item From - sender's address, by default is 'CorrespondAddress';
+ my $Code = do {
+ no strict 'refs';
+ *{ $class . "::GetCurrentUser" }{CODE};
@@ -212,7 +218,7 @@
+ );
+ next if $status > 0;
--=head2 MailError PARAM HASH
+-=item To - recipient, by default is 'OwnerEmail';
+ if ( $status == -2 ) {
+ return (1, $msg, undef);
+ } elsif ( $status == -1 ) {
@@ -224,20 +230,20 @@
+ $parser->RescueOutlook;
+ $parser->_PostProcessNewEntity;
--Sends an error message. Takes a param hash:
+-=item Bcc - optional Bcc recipients;
+ my $head = $Message->head;
+ my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
+ my $Sender = (ParseSenderAddressFromHead( $head ))[0];
-+ my $From = $head->get("From");
++ my $From = Encode::decode( "UTF-8", $head->get("From") );
+ chomp $From if defined $From;
--=over 4
-+ my $MessageId = $head->get('Message-ID')
+-=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 From - sender's address, by default is 'CorrespondAddress';
+-=item Explanation - main content of the error, default value is 'Unexplained error';
+ #Pull apart the subject line
-+ my $Subject = $head->get('Subject') || '';
++ my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || '');
+ chomp $Subject;
+
+ # Lets check for mail loops of various sorts.
@@ -250,7 +256,8 @@
+ MessageId => $MessageId
+ );
--=item To - recipient, by default is 'OwnerEmail';
+-=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) {
@@ -258,15 +265,16 @@
+ }
+ # }}}
--=item Bcc - optional Bcc recipients;
+-=item Attach - optional text that attached to the error as 'message/rfc822' part.
+ $args{'ticket'} ||= ExtractTicketId( $Message );
--=item Subject - subject of the message, default is 'There has been an error';
+-=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 = $Message->head->get('Subject');
++ my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
+ chomp $NewSubject;
--=item Explanation - main content of the error, default value is 'Unexplained error';
+-=back
+ $SystemTicket = RT::Ticket->new( RT->SystemUser );
+ $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
+ if ( $SystemTicket->id ) {
@@ -275,41 +283,10 @@
+ $Right = 'CreateTicket';
+ }
--=item MIMEObj - optional MIME entity that's attached to the error mail, as well we
--add 'In-Reply-To' field to the error that points to this message.
+-=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 );
-+ }
-
--=item Attach - optional text that attached to the error as 'message/rfc822' part.
-+ my ($AuthStat, $CurrentUser, $error) = GetAuthenticationLevel(
-+ MailPlugins => \@mail_plugins,
-+ Actions => \@actions,
-+ Message => $Message,
-+ RawMessageRef => \$args{message},
-+ SystemTicket => $SystemTicket,
-+ SystemQueue => $SystemQueueObj,
-+ );
-
--=item LogLevel - log level under which we should write the subject and
--explanation message into the log, by default we log it as critical.
-+ # If authentication fails and no new user was created, get out.
-+ if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
-
--=back
-+ # If the plugins refused to create one, they lose.
-+ unless ( $AuthStat == -1 ) {
-+ _NoAuthorizedUserFound(
-+ Right => $Right,
-+ Message => $Message,
-+ Requestor => $ErrorsTo,
-+ Queue => $args{'queue'}
-+ );
-
--=cut
-+ }
-+ return ( 0, "Could not load a valid user", undef );
+ }
-sub MailError {
@@ -323,12 +300,50 @@
- 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(
@@ -348,47 +363,42 @@
+ );
+ }
-- # the colons are necessary to make ->build include non-standard headers
-- my %entity_args = (
-- Type => "multipart/mixed",
-- From => $args{'From'},
-- Bcc => $args{'Bcc'},
-- To => $args{'To'},
-- Subject => $args{'Subject'},
-- 'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
+- $entity->attach(
+- Type => "text/plain",
+- Charset => "UTF-8",
+- Data => Encode::encode( "UTF-8", $args{'Explanation'} . "\n" ),
- );
-- # only set precedence if the sysadmin wants us to
-- if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) {
-- $entity_args{'Precedence:'} = RT->Config->Get('DefaultErrorMailPrecedence');
+- if ( $args{'MIMEObj'} ) {
+- $args{'MIMEObj'}->sync_headers;
+- $entity->add_part( $args{'MIMEObj'} );
+ unless ($should_store_machine_generated_message) {
+ return ( 0, $result, undef );
}
-- my $entity = MIME::Entity->build(%entity_args);
-- SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} );
--
-- $entity->attach( Data => $args{'Explanation'} . "\n" );
+- if ( $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;
-- if ( $args{'MIMEObj'} ) {
-- $args{'MIMEObj'}->sync_headers;
-- $entity->add_part( $args{'MIMEObj'} );
-- }
+- SendEmail( Entity => $entity, Bounce => 1 );
+-}
+ my $Ticket = RT::Ticket->new($CurrentUser);
-- if ( $args{'Attach'} ) {
-- $entity->attach( Data => $args{'Attach'}, Type => 'message/rfc822' );
+ if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
+ {
-- }
+-=head2 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ]
+ my @Cc;
+ my @Requestors = ( $CurrentUser->id );
-- SendEmail( Entity => $entity, Bounce => 1 );
--}
+-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,
@@ -397,9 +407,8 @@
+ );
+ }
-+ $head->replace('X-RT-Interface' => 'Email');
-
--=head2 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ]
+-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,
@@ -417,22 +426,19 @@
+ return ( 0, "Ticket creation From: $From failed: $ErrStr", $Ticket );
+ }
--Sends an email (passed as a L<MIME::Entity> object C<ENTITY>) using
--RT's outgoing mail configuration. If C<BOUNCE> is passed, and is a
--true value, the message will be marked as an autogenerated error, if
--possible. Sets Date field of the head to now if it's not set.
+-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;
--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.
+-Returns 1 on success, 0 on error or -1 if message has no recipients
+-and hasn't been sent.
+ } elsif ( $args{'ticket'} ) {
--Ticket and Transaction arguments are optional. If Transaction is
--specified and Ticket is not then ticket of the transaction is
--used, but only if the transaction belongs to a ticket.
+-=head3 Signing and Encrypting
+ $Ticket->Load( $args{'ticket'} );
+ unless ( $Ticket->Id ) {
+ my $error = "Could not find a ticket with id " . $args{'ticket'};
@@ -443,8 +449,11 @@
+ MIMEObj => $Message
+ );
--Returns 1 on success, 0 on error or -1 if message has no recipients
--and hasn't been sent.
+-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;
@@ -452,26 +461,23 @@
+ return ( 1, "Success", $Ticket );
+ }
--=head3 Signing and Encrypting
+-The following precedence of arguments are used to figure out if
+-the message should be encrypted and/or signed:
+ # }}}
--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.
+-* if Sign or Encrypt argument is defined then its value is used
+ my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
+ foreach my $action (@actions) {
--The following precedence of arguments are used to figure out if
--the message should be encrypted and/or signed:
+-* 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) {
--* if Sign or Encrypt argument is defined then its value is used
+-* else properties of a queue of the Ticket are used.
+ #Warn the sender that we couldn't actually submit the comment.
+ MailError(
+ To => $ErrorsTo,
@@ -495,23 +501,20 @@
+ return ( 1, "Success", $Ticket );
+}
--* else if Transaction's first attachment has X-RT-Sign or X-RT-Encrypt
--header field then it's value is used
+-=cut
+=head2 IsCorrectAction
-
--* else properties of a queue of the Ticket are used.
-+Returns a list of valid actions we've found for this message
-
- =cut
-sub WillSignEncrypt {
- my %args = @_;
- my $attachment = delete $args{Attachment};
- my $ticket = delete $args{Ticket};
--
++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;
@@ -601,7 +604,7 @@
+ # Initalize AuthStat so comparisons work correctly
+ $AuthStat = -9999999;
-- my $msgid = $args{'Entity'}->head->get('Message-ID') || '';
+- 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;
@@ -621,7 +624,7 @@
- if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
- and !$args{'Entity'}->head->get("Precedence")
- ) {
-- $args{'Entity'}->head->set( 'Precedence', $precedence );
+- $args{'Entity'}->head->replace( 'Precedence', Encode::encode("UTF-8",$precedence) );
- }
-
- if ( $TransactionObj && !$TicketObj
@@ -635,15 +638,15 @@
- require RT::Date;
- my $date = RT::Date->new( RT->SystemUser );
- $date->SetToNow;
-- $head->set( 'Date', $date->RFC2822( Timezone => 'server' ) );
+- $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->set( 'MIME-Version', '1.0' );
+- $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->set( 'Content-Transfer-Encoding', '8bit' );
+- $head->replace( 'Content-Transfer-Encoding', '8bit' );
- }
-
- if ( RT->Config->Get('Crypt')->{'Enable'} ) {
@@ -674,14 +677,15 @@
- my $Overrides = RT->Config->Get('OverrideOutgoingMailFrom') || {};
-
- if ($TicketObj) {
-- my $QueueName = $TicketObj->QueueObj->Name;
-- my $QueueAddressOverride = $Overrides->{$QueueName};
+- my $Queue = $TicketObj->QueueObj;
+- my $QueueAddressOverride = $Overrides->{$Queue->id}
+- || $Overrides->{$Queue->Name};
-
- if ($QueueAddressOverride) {
- $OutgoingMailAddress = $QueueAddressOverride;
- } else {
-- $OutgoingMailAddress ||= $TicketObj->QueueObj->CorrespondAddress
-- || RT->Config->Get('CorrespondAddress');
+- $OutgoingMailAddress ||= $Queue->CorrespondAddress
+- || RT->Config->Get('CorrespondAddress');
- }
- }
- elsif ($Overrides->{'Default'}) {
@@ -767,7 +771,9 @@
- _RecordSendEmailFailure( $TicketObj );
- }
- return 0;
-- }
++ no strict 'refs';
++ $Code = *{ $_ . "::GetCurrentUser" }{CODE};
+ }
- }
- return 1;
-}
@@ -776,7 +782,7 @@
-
-Loads a template. Parses it using arguments if it's not empty.
-Returns a tuple (L<RT::Template> object, error message).
--
+
-Note that even if a template object is returned MIMEObj method
-may return undef for empty templates.
-
@@ -830,10 +836,10 @@
- return -1;
- }
-
-- $mail->head->set( $_ => Encode::encode_utf8( $args{ $_ } ) )
+- $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ $_ } ) )
- foreach grep defined $args{$_}, qw(To Cc Bcc From);
-
-- $mail->head->set( $_ => $args{ExtraHeaders}{$_} )
+- $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ExtraHeaders}{$_} ) )
- foreach keys %{ $args{ExtraHeaders} };
-
- SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
@@ -877,17 +883,13 @@
- $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
- }
- else {
-- my $txns = $ticket->Transactions;
-- $txns->Limit(
-- FIELD => 'Type',
-- VALUE => $_,
-- ) for qw(Create Correspond);
--
-- while ( my $txn = $txns->Next ) {
-- $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
-+ no strict 'refs';
-+ $Code = *{ $_ . "::GetCurrentUser" }{CODE};
- }
+- $attachments->LimitByTicket( $ticket->id );
+- $attachments->Limit(
+- ALIAS => $attachments->TransactionAlias,
+- FIELD => 'Type',
+- OPERATOR => 'IN',
+- VALUE => [ qw(Create Correspond) ],
+- );
- }
- return $attachments;
-}
@@ -908,7 +910,7 @@
-had been filtered out.
-
-=cut
-
+-
-sub SignEncrypt {
- my %args = (
- Entity => undef,
@@ -918,7 +920,7 @@
- );
- return 1 unless $args{'Sign'} || $args{'Encrypt'};
-
-- my $msgid = $args{'Entity'}->head->get('Message-ID') || '';
+- my $msgid = Encode::decode( "UTF-8", $args{'Entity'}->head->get('Message-ID') || '' );
- chomp $msgid;
-
- $RT::Logger->debug("$msgid Signing message") if $args{'Sign'};
@@ -1073,13 +1075,8 @@
- $max = int( $max / 3 ) * 3;
-
- chomp $value;
--
+
- if ( $max <= 0 ) {
-
-- # gives an error...
-- $RT::Logger->crit("Can't encode! Charset or encoding too big.");
-- return ($value);
-- }
+ # Notify the RT Admin of the failure.
+ MailError(
+ To => RT->Config->Get('OwnerEmail'),
@@ -1088,18 +1085,20 @@
+RT could not load a valid user, and RT's configuration does not allow
+for the creation of a new user for this email (@{[$args{Requestor}]}).
-- return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
+- # gives an error...
+- $RT::Logger->crit("Can't encode! Charset or encoding too big.");
+- return ($value);
+- }
+You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
+queue @{[$args{'Queue'}]}.
-- $value =~ s/\s+$//;
+- return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
+EOT
+ MIMEObj => $args{'Message'},
+ LogLevel => 'error'
+ );
-- # we need perl string to split thing char by char
-- Encode::_utf8_on($value) unless Encode::is_utf8($value);
+- $value =~ s/\s+$//;
+ # Also notify the requestor that his request has been dropped.
+ if ($args{'Requestor'} ne RT->Config->Get('OwnerEmail')) {
+ MailError(
@@ -1142,10 +1141,21 @@
Takes a hash containing QueueObj, Head and CurrentUser objects.
@@
-
- =cut
-
--sub ParseErrorsToAddressFromHead {
+ push @errors, "$header: $addr_line";
+ }
+
+- return (undef, undef, @errors);
++ return (undef, undef, @errors);
++}
++
++=head2 ParseErrorsToAddressFromHead HEAD
++
++Takes a MIME::Header object. Return a single value : user at host
++of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
++From:, Sender)
++
++=cut
++
+sub ParseErrorsToAddressFromHead {
+ my $head = shift;
+
@@ -1154,7 +1164,7 @@
+ foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
+
+ # If there's a header of that name
-+ my $headerobj = $head->get($header);
++ my $headerobj = Encode::decode( "UTF-8", $head->get($header) );
+ if ($headerobj) {
+ my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
+
@@ -1261,7 +1271,7 @@
+ # to the scrip. We might want to notify nobody. Or just
+ # the RT Owner. Or maybe all Privileged watchers.
+ my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
-+ $head->replace( 'RT-Squelch-Replies-To', $Sender );
++ $head->replace( 'RT-Squelch-Replies-To', Encode::encode("UTF-8", $Sender ) );
+ $head->replace( 'RT-DetectedAutoGenerated', 'true' );
+ }
+ return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
@@ -1276,66 +1286,76 @@
+=cut
+
+sub CheckForLoops {
- my $head = shift;
-
-- #Figure out who's sending this message.
++ my $head = shift;
++
+ # If this instance of RT sent it our, we don't want to take it in
-+ my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
++ 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;
+ }
-
-- foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
++
+ # TODO: We might not trap the case where RT instance A sends a mail
+ # to RT instance B which sends a mail to ...
+ return undef;
-+}
-
+ }
+
+-=head2 ParseErrorsToAddressFromHead HEAD
++=head2 CheckForSuspiciousSender HEAD
+
+-Takes a MIME::Header object. Return a single value : user at host
+-of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
+-From:, Sender)
++Takes a HEAD object of L<MIME::Head> class and returns true if sender
++is suspicious. Suspicious means mailer daemon.
++
++See also L</ParseSenderAddressFromHead>.
+
+ =cut
+
+-sub ParseErrorsToAddressFromHead {
++sub CheckForSuspiciousSender {
+ my $head = shift;
+
+- #Figure out who's sending this message.
+-
+- foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
+-
- # If there's a header of that name
-- my $headerobj = $head->get($header);
+- my $headerobj = Encode::decode( "UTF-8", $head->get($header) );
- if ($headerobj) {
- my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
-+=head2 CheckForSuspiciousSender HEAD
-
+-
- # If it's got actual useful content...
- return ($addr) if ($addr);
- }
- }
-}
-+Takes a HEAD object of L<MIME::Head> class and returns true if sender
-+is suspicious. Suspicious means mailer daemon.
-+
-+See also L</ParseSenderAddressFromHead>.
-
-+=cut
-
-+sub CheckForSuspiciousSender {
-+ my $head = shift;
+-
+-
++ #if it's from a postmaster or mailer daemon, it's likely a bounce.
-=head2 ParseAddressFromHeader ADDRESS
-+ #if it's from a postmaster or mailer daemon, it's likely a bounce.
-
--Takes an address from C<$head->get('Line')> and returns a tuple: user at host, friendly name
+ #TODO: better algorithms needed here - there is no standards for
+ #bounces, so it's very difficult to separate them from anything
+ #else. At the other hand, the Return-To address is only ment to be
+ #used as an error channel, we might want to put up a separate
+ #Return-To address which is treated differently.
+-Takes an address from C<$head->get('Line')> and returns a tuple: user at host, friendly name
++ #TODO: search through the whole email and find the right Ticket ID.
+
-=cut
-+ #TODO: search through the whole email and find the right Ticket ID.
++ my ( $From, $junk ) = ParseSenderAddressFromHead($head);
-sub ParseAddressFromHeader {
- my $Addr = shift;
-+ my ( $From, $junk ) = ParseSenderAddressFromHead($head);
++ # If unparseable (non-ASCII), $From can come back undef
++ return undef if not defined $From;
- # Some broken mailers send: ""Vincent, Jesse"" <jesse at fsck.com>. Hate
- $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;
- my @Addresses = RT::EmailParser->ParseEmailAddress($Addr);
-+ # If 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 "" ))
@@ -1356,9 +1376,9 @@
-Gets a head object and list of addresses.
-Deletes addresses from To, Cc or Bcc fields.
-+Takes a HEAD object of L<MIME::Head> class and returns true if message
-+is autogenerated. Checks 'Precedence' and 'X-FC-Machinegenerated'
-+fields of the head in tests.
++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
@@ -1368,12 +1388,11 @@
- my %skip = map { lc $_ => 1 } @_;
- foreach my $field ( qw(To Cc Bcc) ) {
-- $head->set( $field =>
+- $head->replace( $field => Encode::encode( "UTF-8",
- join ', ', map $_->format, grep !$skip{ lc $_->address },
-- Email::Address->parse( $head->get( $field ) )
+- Email::Address->parse( Encode::decode( "UTF-8", $head->get( $field ) ) ) )
- );
-+ my $Precedence = $head->get("Precedence") || "";
-+ if ( $Precedence =~ /^(bulk|junk)/i ) {
++ if (grep { /^(bulk|junk)/i } $head->get_all("Precedence")) {
+ return (1);
}
-}
@@ -1406,7 +1425,7 @@
- my $get_header = sub {
- my @res;
- if ( $args{'InReplyTo'}->isa('MIME::Entity') ) {
-- @res = $args{'InReplyTo'}->head->get( shift );
+- @res = map {Encode::decode("UTF-8", $_)} $args{'InReplyTo'}->head->get( shift );
- } else {
- @res = $args{'InReplyTo'}->GetHeader( shift ) || '';
- }
@@ -1439,14 +1458,15 @@
- if @references > 10;
- my $mail = $args{'Message'};
-- $mail->head->set( 'In-Reply-To' => Encode::encode_utf8(join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
-- $mail->head->set( 'References' => Encode::encode_utf8(join ' ', @references) );
+- $mail->head->replace( 'In-Reply-To' => Encode::encode( "UTF-8", join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
+- $mail->head->replace( 'References' => Encode::encode( "UTF-8", join ' ', @references) );
+ return (0);
}
-sub PseudoReference {
- my $ticket = shift;
- return '<RT-Ticket-'. $ticket->id .'@'. RT->Config->Get('Organization') .'>';
++
+sub CheckForBounce {
+ my $head = shift;
+
@@ -1476,7 +1496,7 @@
+ @_
+ );
+
-+ my $From = $args{Message}->head->get("From");
++ 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 );
@@ -1512,7 +1532,13 @@
- my $queue_tag = $ticket->QueueObj->SubjectTag;
+ return ( 1, "Success" );
+}
-+
+
+- my $tag_re = RT->Config->Get('EmailSubjectTagRegex');
+- unless ( $tag_re ) {
+- my $tag = $queue_tag || RT->Config->Get('rtname');
+- $tag_re = qr/\Q$tag\E/;
+- } elsif ( $queue_tag ) {
+- $tag_re = qr/$tag_re|\Q$queue_tag\E/;
+=head2 MailError PARAM HASH
+
+Sends an error message. Takes a param hash:
@@ -1562,22 +1588,17 @@
+ # the colons are necessary to make ->build include non-standard headers
+ my %entity_args = (
+ Type => "multipart/mixed",
-+ From => $args{'From'},
-+ Bcc => $args{'Bcc'},
-+ To => $args{'To'},
-+ Subject => $args{'Subject'},
-+ 'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
++ 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') ),
+ );
-
-- my $tag_re = RT->Config->Get('EmailSubjectTagRegex');
-- unless ( $tag_re ) {
-- my $tag = $queue_tag || RT->Config->Get('rtname');
-- $tag_re = qr/\Q$tag\E/;
-- } elsif ( $queue_tag ) {
-- $tag_re = qr/$tag_re|\Q$queue_tag\E/;
++
+ # only set precedence if the sysadmin wants us to
+ if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) {
-+ $entity_args{'Precedence:'} = RT->Config->Get('DefaultErrorMailPrecedence');
++ $entity_args{'Precedence:'} =
++ Encode::encode( "UTF-8", RT->Config->Get('DefaultErrorMailPrecedence') );
}
- return $subject if $subject =~ /\[$tag_re\s+#$id\]/;
@@ -1588,7 +1609,11 @@
+ my $entity = MIME::Entity->build(%entity_args);
+ SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} );
-+ $entity->attach( Data => $args{'Explanation'} . "\n" );
++ $entity->attach(
++ Type => "text/plain",
++ Charset => "UTF-8",
++ Data => Encode::encode( "UTF-8", $args{'Explanation'} . "\n" ),
++ );
-=head2 Gateway ARGSREF
+ if ( $args{'MIMEObj'} ) {
@@ -1597,7 +1622,7 @@
+ }
+ if ( $args{'Attach'} ) {
-+ $entity->attach( Data => $args{'Attach'}, Type => 'message/rfc822' );
++ $entity->attach( Data => Encode::encode( "UTF-8", $args{'Attach'} ), Type => 'message/rfc822' );
-Takes parameters:
+ }
@@ -1708,7 +1733,7 @@
- message => undef,
- %$argsref
- );
-+ my $msgid = $args{'Entity'}->head->get('Message-ID') || '';
++ 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;
@@ -1741,7 +1766,7 @@
+ if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
+ and !$args{'Entity'}->head->get("Precedence")
+ ) {
-+ $args{'Entity'}->head->set( 'Precedence', $precedence );
++ $args{'Entity'}->head->replace( 'Precedence', Encode::encode("UTF-8",$precedence) );
}
- my $parser = RT::EmailParser->new();
@@ -1768,15 +1793,15 @@
+ require RT::Date;
+ my $date = RT::Date->new( RT->SystemUser );
+ $date->SetToNow;
-+ $head->set( 'Date', $date->RFC2822( Timezone => 'server' ) );
++ $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->set( 'MIME-Version', '1.0' );
++ $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->set( 'Content-Transfer-Encoding', '8bit' );
++ $head->replace( 'Content-Transfer-Encoding', '8bit' );
+ }
- return ( 0,
@@ -1829,14 +1854,15 @@
+ my $Overrides = RT->Config->Get('OverrideOutgoingMailFrom') || {};
+
+ if ($TicketObj) {
-+ my $QueueName = $TicketObj->QueueObj->Name;
-+ my $QueueAddressOverride = $Overrides->{$QueueName};
++ my $Queue = $TicketObj->QueueObj;
++ my $QueueAddressOverride = $Overrides->{$Queue->id}
++ || $Overrides->{$Queue->Name};
+
+ if ($QueueAddressOverride) {
+ $OutgoingMailAddress = $QueueAddressOverride;
+ } else {
-+ $OutgoingMailAddress ||= $TicketObj->QueueObj->CorrespondAddress
-+ || RT->Config->Get('CorrespondAddress');
++ $OutgoingMailAddress ||= $Queue->CorrespondAddress
++ || RT->Config->Get('CorrespondAddress');
+ }
+ }
+ elsif ($Overrides->{'Default'}) {
@@ -1857,18 +1883,18 @@
+ $from =~ s/\s//g;
+ push @args, "-f", "$prefix$from\@$domain";
+ }
-+
+
+- $skip_plugin{ $class }++;
+ eval {
+ # don't ignore CHLD signal to get proper exit code
+ local $SIG{'CHLD'} = 'DEFAULT';
-- $skip_plugin{ $class }++;
-+ # if something wrong with $mail->print we will get PIPE signal, handle it
-+ local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
-
- my $Code = do {
- no strict 'refs';
- *{ $class . "::GetCurrentUser" }{CODE};
++ # if something wrong with $mail->print we will get PIPE signal, handle it
++ local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
++
+ require IPC::Open2;
+ my ($mail, $stdout);
+ my $pid = IPC::Open2::open2( $stdout, $mail, $path, @args )
@@ -1941,17 +1967,17 @@
- my $head = $Message->head;
- my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
- my $Sender = (ParseSenderAddressFromHead( $head ))[0];
-- my $From = $head->get("From");
+- my $From = Encode::decode( "UTF-8", $head->get("From") );
- chomp $From if defined $From;
+=head2 PrepareEmailUsingTemplate Template => '', Arguments => {}
-- my $MessageId = $head->get('Message-ID')
+- my $MessageId = Encode::decode( "UTF-8", $head->get('Message-ID') )
- || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
+Loads a template. Parses it using arguments if it's not empty.
+Returns a tuple (L<RT::Template> object, error message).
- #Pull apart the subject line
-- my $Subject = $head->get('Subject') || '';
+- my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || '');
- chomp $Subject;
-
- # Lets check for mail loops of various sorts.
@@ -1991,7 +2017,7 @@
+ return (undef, $msg) unless $status;
- # ExtractTicketId may have been overridden, and edited the Subject
-- my $NewSubject = $Message->head->get('Subject');
+- my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
- chomp $NewSubject;
+ return $template;
+}
@@ -2073,10 +2099,10 @@
- undef
- );
- }
-+ $mail->head->set( $_ => Encode::encode_utf8( $args{ $_ } ) )
++ $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ $_ } ) )
+ foreach grep defined $args{$_}, qw(To Cc Bcc From);
-+ $mail->head->set( $_ => $args{ExtraHeaders}{$_} )
++ $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ExtraHeaders}{$_} ) )
+ foreach keys %{ $args{ExtraHeaders} };
- unless ($should_store_machine_generated_message) {
@@ -2084,21 +2110,27 @@
- }
+ SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
+- $head->replace('X-RT-Interface' => 'Email');
++ return SendEmail( Entity => $mail );
++}
+
- # if plugin's updated SystemTicket then update arguments
- $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
-+ return SendEmail( Entity => $mail );
-+}
++=head2 GetForwardFrom Ticket => undef, Transaction => undef
- my $Ticket = RT::Ticket->new($CurrentUser);
-+=head2 GetForwardFrom Ticket => undef, Transaction => undef
++Resolve the From field to use in forward mail
- if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
- {
-+Resolve the From field to use in forward mail
++=cut
- my @Cc;
- my @Requestors = ( $CurrentUser->id );
-+=cut
++sub GetForwardFrom {
++ my %args = ( Ticket => undef, Transaction => undef, @_ );
++ my $txn = $args{Transaction};
++ my $ticket = $args{Ticket} || $txn->Object;
- if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
- @Cc = ParseCcAddressesFromHead(
@@ -2107,12 +2139,6 @@
- QueueObj => $SystemQueueObj
- );
- }
-+sub GetForwardFrom {
-+ my %args = ( Ticket => undef, Transaction => undef, @_ );
-+ my $txn = $args{Transaction};
-+ my $ticket = $args{Ticket} || $txn->Object;
-
-- $head->replace('X-RT-Interface' => 'Email');
+ if ( RT->Config->Get('ForwardFromUser') ) {
+ return ( $txn || $ticket )->CurrentUser->EmailAddress;
+ }
@@ -2145,14 +2171,9 @@
- @actions = grep !/^(comment|correspond)$/, @actions;
- $args{'ticket'} = $id;
+Resolve the Attachments to forward
-+
+
+- } elsif ( $args{'ticket'} ) {
+=cut
-
-- } elsif ( $args{'ticket'} ) {
-+sub GetForwardAttachments {
-+ my %args = ( Ticket => undef, Transaction => undef, @_ );
-+ my $txn = $args{Transaction};
-+ my $ticket = $args{Ticket} || $txn->Object;
- $Ticket->Load( $args{'ticket'} );
- unless ( $Ticket->Id ) {
@@ -2163,24 +2184,28 @@
- Explanation => $error,
- MIMEObj => $Message
- );
++sub GetForwardAttachments {
++ my %args = ( Ticket => undef, Transaction => undef, @_ );
++ my $txn = $args{Transaction};
++ my $ticket = $args{Ticket} || $txn->Object;
+
+- return ( 0, $error );
+- }
+- $args{'ticket'} = $Ticket->id;
+- } else {
+- return ( 1, "Success", $Ticket );
+ my $attachments = RT::Attachments->new( $ticket->CurrentUser );
+ if ($txn) {
+ $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
+ }
+ else {
-+ my $txns = $ticket->Transactions;
-+ $txns->Limit(
-+ FIELD => 'Type',
-+ VALUE => $_,
-+ ) for qw(Create Correspond);
-
-- return ( 0, $error );
-+ while ( my $txn = $txns->Next ) {
-+ $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id );
- }
-- $args{'ticket'} = $Ticket->id;
-- } else {
-- return ( 1, "Success", $Ticket );
++ $attachments->LimitByTicket( $ticket->id );
++ $attachments->Limit(
++ ALIAS => $attachments->TransactionAlias,
++ FIELD => 'Type',
++ OPERATOR => 'IN',
++ VALUE => [ qw(Create Correspond) ],
++ );
}
+ return $attachments;
+}
@@ -2288,7 +2313,7 @@
- # if plugin returns AuthStat -2 we skip action
- # NOTE: this is experimental API and it would be changed
- my %skip_action = ();
-+ my $msgid = $args{'Entity'}->head->get('Message-ID') || '';
++ my $msgid = Encode::decode( "UTF-8", $args{'Entity'}->head->get('Message-ID') || '' );
+ chomp $msgid;
- # Since this needs loading, no matter what
@@ -2392,7 +2417,7 @@
+ $RT::Logger->error("Couldn't send 'Error to RT owner: public key'");
+ }
-- my $From = $args{Message}->head->get("From");
+- 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 );
@@ -2435,22 +2460,16 @@
+ return -1;
}
- return ( 1, "Success" );
-+
+-}
+-
+-=head2 _NoAuthorizedUserFound
+
+-Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
+-
+-=cut
+ # redo without broken recipients
+ %res = RT::Crypt->SignEncrypt( %args );
+ return 0 if $res{'exit_code'};
-+
-+ return 1;
- }
-
--=head2 _NoAuthorizedUserFound
-+=head2 DeleteRecipientsFromHead HEAD RECIPIENTS
-
--Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
-+Gets a head object and list of addresses.
-+Deletes addresses from To, Cc or Bcc fields.
-
- =cut
-sub _NoAuthorizedUserFound {
- my %args = (
@@ -2460,9 +2479,8 @@
- Queue => undef,
- @_
- );
-+sub DeleteRecipientsFromHead {
-+ my $head = shift;
-+ my %skip = map { lc $_ => 1 } @_;
++ return 1;
++}
- # Notify the RT Admin of the failure.
- MailError(
@@ -2471,24 +2489,18 @@
- Explanation => <<EOT,
-RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for this email (@{[$args{Requestor}]}).
-+ foreach my $field ( qw(To Cc Bcc) ) {
-+ $head->set( $field =>
-+ join ', ', map $_->format, grep !$skip{ lc $_->address },
-+ Email::Address->parse( $head->get( $field ) )
-+ );
-+ }
-+}
++=head2 DeleteRecipientsFromHead HEAD RECIPIENTS
-You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
-queue @{[$args{'Queue'}]}.
-+=head2 EncodeToMIME
++Gets a head object and list of addresses.
++Deletes addresses from To, Cc or Bcc fields.
-EOT
- MIMEObj => $args{'Message'},
- LogLevel => 'error'
- );
-+Takes a hash with a String and a Charset. Returns the string encoded
-+according to RFC2047, using B (base64 based) encoding.
++=cut
- # Also notify the requestor that his request has been dropped.
- if ($args{'Requestor'} ne RT->Config->Get('OwnerEmail')) {
@@ -2498,37 +2510,62 @@
- Explanation => <<EOT,
-RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for your email.
-+String must be a perl string, octets are returned.
++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
-+
+
+ =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';
--=head2 _HandleMachineGeneratedMail
+- my $IsBounce = CheckForBounce($head);
+ # using RFC2047 notation, sec 2.
+ # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
--Takes named params:
-- Message
-- ErrorsTo
-- Subject
+- 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
@@ -2544,34 +2581,28 @@
+ );
+ $max = int( $max / 3 ) * 3;
--Checks the message to see if it's a bounce, if it looks like a loop, if it's autogenerated, etc.
--Returns a triple of ("Should we continue (boolean)", "New value for $ErrorsTo", "Status message",
--"This message appears to be a loop (boolean)" );
+- my $IsSuspiciousSender = CheckForSuspiciousSender($head);
+ chomp $value;
--=cut
+- my $IsALoop = CheckForLoops($head);
+ if ( $max <= 0 ) {
--sub _HandleMachineGeneratedMail {
-- my %args = ( Message => undef, ErrorsTo => undef, Subject => undef, MessageId => undef, @_ );
-- my $head = $args{'Message'}->head;
-- my $ErrorsTo = $args{'ErrorsTo'};
+- my $SquelchReplies = 0;
+ # gives an error...
+ $RT::Logger->crit("Can't encode! Charset or encoding too big.");
+ return ($value);
+ }
-- my $IsBounce = CheckForBounce($head);
+- my $owner_mail = RT->Config->Get('OwnerEmail');
+ return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
-- my $IsAutoGenerated = CheckForAutoGenerated($head);
+- #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 $IsSuspiciousSender = CheckForSuspiciousSender($head);
-+ # we need perl string to split thing char by char
-+ Encode::_utf8_on($value) unless Encode::is_utf8($value);
-
-- my $IsALoop = CheckForLoops($head);
++
+ my ( $tmp, @chunks ) = ( '', () );
+ while ( length $value ) {
+ my $char = substr( $value, 0, 1, '' );
@@ -2581,10 +2612,12 @@
+ $tmp = '';
+ }
+ $tmp .= $octets;
-+ }
+ }
+ push @chunks, $tmp if length $tmp;
-- my $SquelchReplies = 0;
+- # 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 ),
@@ -2592,7 +2625,15 @@
+ return ($value);
+}
-- my $owner_mail = RT->Config->Get('OwnerEmail');
+- #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,
@@ -2605,19 +2646,23 @@
+ my $scrip_id = ( ref $args{'Scrip'}? $args{'Scrip'}->id : $args{'Scrip'} ) || 0;
+ my $sent = ( ref $args{'ScripAction'}? $args{'ScripAction'}->{'_Message_ID'} : 0 ) || 0;
-- #If the message is autogenerated, we need to know, so we can not
-- # send mail to the sender
-- if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
-- $SquelchReplies = 1;
-- $ErrorsTo = $owner_mail;
+- #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 .">" ;
+}
-- # 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.");
+- # 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,
@@ -2627,27 +2672,25 @@
+ );
+ return unless $args{'Message'} && $args{'InReplyTo'};
-- #Should we mail it to RTOwner?
-- if ( RT->Config->Get('LoopsToRTOwner') ) {
-- MailError(
-- To => $owner_mail,
-- Subject => "RT Bounce: ".$args{'Subject'},
-- Explanation => "RT thinks this message may be a bounce",
-- MIMEObj => $args{Message}
-- );
+- if ($SquelchReplies) {
+ my $get_header = sub {
+ my @res;
+ if ( $args{'InReplyTo'}->isa('MIME::Entity') ) {
-+ @res = $args{'InReplyTo'}->head->get( shift );
++ @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;
+ };
-- #Do we actually want to store it?
-- return ( 0, $ErrorsTo, "Message Bounced", $IsALoop )
-- unless RT->Config->Get('StoreLoops');
+- # 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');
@@ -2655,38 +2698,18 @@
+ unless ( @references ) {
+ @references = $get_header->('In-Reply-To');
}
--
-- # Squelch replies if necessary
-- # Don't let the user stuff the RT-Squelch-Replies-To header.
-- if ( $head->get('RT-Squelch-Replies-To') ) {
-- $head->replace(
-- 'RT-Relocated-Squelch-Replies-To',
-- $head->get('RT-Squelch-Replies-To')
-- );
-- $head->delete('RT-Squelch-Replies-To');
+- 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;
-
-- if ($SquelchReplies) {
--
-- # Squelch replies to the sender, and also leave a clue to
-- # allow us to squelch ALL outbound messages. This way we
-- # can punt the logic of "what to do when we get a bounce"
-- # to the scrip. We might want to notify nobody. Or just
-- # the RT Owner. Or maybe all Privileged watchers.
-- my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
-- $head->replace( 'RT-Squelch-Replies-To', $Sender );
-- $head->replace( 'RT-DetectedAutoGenerated', 'true' );
-- }
-- return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
++
+ my $mail = $args{'Message'};
-+ $mail->head->set( 'In-Reply-To' => Encode::encode_utf8(join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
-+ $mail->head->set( 'References' => Encode::encode_utf8(join ' ', @references) );
++ $mail->head->replace( 'In-Reply-To' => Encode::encode( "UTF-8", join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
++ $mail->head->replace( 'References' => Encode::encode( "UTF-8", join ' ', @references) );
}
-=head2 IsCorrectAction
3: 670a057 ! 3: 2a14662 Adjust POD headers to reflect split
@@ -113,8 +113,8 @@
-=head2 CheckForAutoGenerated HEAD
+=head3 CheckForAutoGenerated HEAD
- Takes a HEAD object of L<MIME::Head> class and returns true if message
- is autogenerated. Checks 'Precedence' and 'X-FC-Machinegenerated'
+ Takes a HEAD object of L<MIME::Head> class and returns true if message is
+ autogenerated. Checks C<Precedence>, C<Auto-Submitted>, and
@@
return ( 1, "Success" );
}
@@ -205,6 +205,6 @@
-=head2 ConvertHTMLToText HTML
+=head3 ConvertHTMLToText HTML
- Takes HTML and converts it to plain text. Appropriate for generating a
- plain text part from an HTML part of an email. Returns undef if
+ Takes HTML characters and converts it to plain text characters.
+ Appropriate for generating a plain text part from an HTML part of an
4: 5dfc009 = 4: 99624a2 Update MailPlugins to only reference existant modules
5: b9ed3c2 = 5: 00d0850 Move MailPlugins documentation out of rt-mailgate, where is is mostly irrelevant
6: e3d18e3 ! 6: 8400ff9 Remove duplicate ParseCcAddressesFromHead in RT::EmailParser
@@ -47,8 +47,8 @@
-
- my (@Addresses);
-
-- my @ToObjs = Email::Address->parse( $self->Head->get('To') );
-- my @CcObjs = Email::Address->parse( $self->Head->get('Cc') );
+- 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;
7: a93449c ! 7: 7b68589 Use Scope::Upper to allow returns from Gateway from nested subs
@@ -15,8 +15,8 @@
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@
- use Mail::Mailer ();
- use Text::ParseWords qw/shellwords/;
+ use RT::Util 'safe_run_child';
+ use File::Spec;
use MIME::Words ();
+use Scope::Upper qw/unwind HERE/;
@@ -148,8 +148,8 @@
+ FAILURE( $result )
+ unless $should_store_machine_generated_message;
- # if plugin's updated SystemTicket then update arguments
- $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
+ $head->replace('X-RT-Interface' => 'Email');
+
@@
Explanation => $ErrStr,
MIMEObj => $Message
8: f993bae = 8: d358013 Add a base email plugin role, which provides TMPFAIL/FAILURE/SUCCESS
9: c2e65a5 = 9: aee7869 Check if each named mail plugin DOES the mail plugin role
10: a997949 ! 10: 049f74d Return values of ApplyBeforeDecode plugins' GetCurrentUser are irrelevant
@@ -44,7 +44,7 @@
# attach the original encrypted message
@@
my %seen;
- $args{'Message'}->head->replace( 'X-RT-Privacy' => $_ )
+ $args{'Message'}->head->replace( 'X-RT-Privacy' => Encode::encode( "UTF-8", $_ ) )
foreach grep !$seen{$_}++, @found;
-
- return 1;
11: 588a509 ! 11: cae6a89 Push bounce short-circuiting down into _HandleMachineGeneratedMail
@@ -47,9 +47,9 @@
- FAILURE( $result )
- unless $should_store_machine_generated_message;
-
+ $head->replace('X-RT-Interface' => 'Email');
+
# if plugin's updated SystemTicket then update arguments
- $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
-
@@
}
@@ -61,7 +61,7 @@
# Squelch replies if necessary
@@
- $head->replace( 'RT-Squelch-Replies-To', $Sender );
+ $head->replace( 'RT-Squelch-Replies-To', Encode::encode("UTF-8", $Sender ) );
$head->replace( 'RT-DetectedAutoGenerated', 'true' );
}
- return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
12: 1887b21 ! 12: afe6659 $IsALoop is now unused in Gateway
@@ -16,7 +16,7 @@
ErrorsTo => $ErrorsTo,
Subject => $Subject,
@@
- $head->replace( 'RT-Squelch-Replies-To', $Sender );
+ $head->replace( 'RT-Squelch-Replies-To', Encode::encode("UTF-8", $Sender ) );
$head->replace( 'RT-DetectedAutoGenerated', 'true' );
}
- return ( $ErrorsTo, $IsALoop );
13: 314ad1b = 13: dc1f8fe Stop moving RT-Squelch-Replies-To aside
14: 19607b9 ! 14: 03979a3 Simplify _HandleMachineGeneratedMail logic
@@ -16,11 +16,11 @@
my $head = $Message->head;
- my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
my $Sender = (ParseSenderAddressFromHead( $head ))[0];
- my $From = $head->get("From");
+ my $From = Encode::decode( "UTF-8", $head->get("From") );
chomp $From if defined $From;
@@
#Pull apart the subject line
- my $Subject = $head->get('Subject') || '';
+ my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || '');
chomp $Subject;
-
+
@@ -99,7 +99,7 @@
+ # to the scrip. We might want to notify nobody. Or just
+ # the RT Owner. Or maybe all Privileged watchers.
+ my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
-+ $head->replace( 'RT-Squelch-Replies-To', $Sender );
++ $head->replace( 'RT-Squelch-Replies-To', Encode::encode("UTF-8", $Sender ) );
+ $head->replace( 'RT-DetectedAutoGenerated', 'true' );
- # Squelch replies to the sender, and also leave a clue to
@@ -108,7 +108,7 @@
- # to the scrip. We might want to notify nobody. Or just
- # the RT Owner. Or maybe all Privileged watchers.
- my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
-- $head->replace( 'RT-Squelch-Replies-To', $Sender );
+- $head->replace( 'RT-Squelch-Replies-To', Encode::encode("UTF-8", $Sender ) );
- $head->replace( 'RT-DetectedAutoGenerated', 'true' );
- }
- return ( $ErrorsTo );
15: 531b96b = 15: 1a1d88f Use different method names rather than an ApplyBeforeDecode method
16: 70daad9 = 16: 202a69f Use FAILURE to abort from GetCurrentUser, rather than a magic -1 value
17: 5a0942b = 17: 19f7ec5 Remove the unused $error variable
18: 8cabccc = 18: 332f4d2 Move $Right to where it is used
19: 52e24c3 ! 19: a418722 Move SystemTicket definition to where it is first used
@@ -15,7 +15,7 @@
my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
TMPFAIL(
@@
- my $NewSubject = $Message->head->get('Subject');
+ my $NewSubject = Encode::decode( "UTF-8", $Message->head->get('Subject') );
chomp $NewSubject;
- $SystemTicket = RT::Ticket->new( RT->SystemUser );
20: 6f62839 ! 20: 8ae942f Move NewSubject to where it is used
@@ -10,18 +10,18 @@
$args{'ticket'} ||= ExtractTicketId( $Message );
- # ExtractTicketId may have been overridden, and edited the Subject
-- my $NewSubject = $Message->head->get('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;
@@
-
- $head->replace('X-RT-Interface' => 'Email');
+ );
+ }
+ # ExtractTicketId may have been overridden, and edited the Subject
-+ my $NewSubject = $head->get('Subject');
++ my $NewSubject = Encode::decode( "UTF-8", $head->get('Subject') );
+ chomp $NewSubject;
+
my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
21: 6d715aa = 21: 24bf62a Remove CreateUser, merging to form a more featureful LoadOrCreateByEmail
22: e9e5e8b ! 22: 6dd0a29 Always create the user; this simplifies ACL checking greatly
@@ -25,6 +25,24 @@
Unprivileged users in such cases is worth the notable code
simplification.
+diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
+--- a/docs/UPGRADING-4.4
++++ b/docs/UPGRADING-4.4
+@@
+
+ 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
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
23: e82d433 ! 23: 39d8b60 Split authentication from authorization
@@ -13,11 +13,10 @@
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@
-
- RT::Interface::Email no longer exports functions.
+ 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>
24: d1c4c8b = 24: d6b673a Remove no-longer-used _NoAuthorizedUserFound
25: afadc73 = 25: 7408373 Remove now-unused $Right variable, previously used by _NoAuthorizedUserFound
26: 73f1c62 ! 26: ddd1c03 Fail if the first action is unauthenticated
@@ -17,7 +17,7 @@
my $head = $Message->head;
- my $Sender = (ParseSenderAddressFromHead( $head ))[0];
- my $From = $head->get("From");
+ my $From = Encode::decode( "UTF-8", $head->get("From") );
chomp $From if defined $From;
@@
@@ -53,9 +53,9 @@
- );
- }
-
+ $head->replace('X-RT-Interface' => 'Email');
+
# if plugin's updated SystemTicket then update arguments
- $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
-
@@
sub CheckACL {
27: a5fc795 = 27: 03d0af9 Notify the owner on common mis-configurations
28: 6163947 = 28: 12115ef Remove extra error in mail-gateway
29: 87b474c = 29: 096152b Add back a warning that is now lacking
30: c9d8ab4 ! 30: 3079bf8 Stop passing mail plugins around; load them lazily in one Plugins() method
@@ -6,7 +6,7 @@
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@
- use Text::ParseWords qw/shellwords/;
+ use File::Spec;
use MIME::Words ();
use Scope::Upper qw/unwind HERE/;
+use 5.010;
31: 60d8040 ! 31: 5ccf5a1 Split action handling into plugin classes
@@ -106,8 +106,8 @@
my $parser = RT::EmailParser->new();
$parser->SmartParseMIMEEntityFromScalar(
@@
- Queue => $SystemQueueObj,
- );
+
+ $head->replace('X-RT-Interface' => 'Email');
- # if plugin's updated SystemTicket then update arguments
- $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
@@ -128,10 +128,8 @@
- );
- }
-
-- $head->replace('X-RT-Interface' => 'Email');
--
- # ExtractTicketId may have been overridden, and edited the Subject
-- my $NewSubject = $head->get('Subject');
+- my $NewSubject = Encode::decode( "UTF-8", $head->get('Subject') );
- chomp $NewSubject;
-
- my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
@@ -281,7 +279,8 @@
- return
- grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ),
- map lc $user->CanonicalizeEmailAddress( $_->address ),
-- map RT::EmailParser->CleanupAddresses( Email::Address->parse( $args{'Head'}->get( $_ ) ) ),
+- 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);
@@ -306,7 +305,7 @@
- @_
- );
-
-- my $From = $args{Message}->head->get("From");
+- 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 );
@@ -428,14 +427,13 @@
+ @Cc =
+ grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ),
+ map lc $user->CanonicalizeEmailAddress( $_->address ),
-+ map RT::EmailParser->CleanupAddresses( Email::Address->parse( $head->get( $_ ) ) ),
++ map RT::EmailParser->CleanupAddresses( Email::Address->parse(
++ Encode::decode( "UTF-8", $head->get( $_ ) ) ) ),
+ qw(To Cc);
+ }
+
-+ $head->replace('X-RT-Interface' => 'Email');
-+
+ # ExtractTicketId may have been overridden, and edited the Subject
-+ my $subject = $head->get('Subject');
++ my $subject = Encode::decode( "UTF-8", $head->get('Subject') );
+ chomp $subject;
+
+ my ( $id, $Transaction, $ErrStr ) = $args{Ticket}->Create(
@@ -578,7 +576,7 @@
+ FAILURE( $error );
+ }
+
-+ my $From = $args{Message}->head->get("From");
++ my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
+
+ my $new_status = $args{'Ticket'}->FirstInactiveStatus;
+ return unless $new_status;
@@ -680,7 +678,7 @@
+ FAILURE( $error );
+ }
+
-+ my $From = $args{Message}->head->get("From");
++ my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
+
+ my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{Ticket}->CurrentUser->id );
+ return if $status;
32: b707acb ! 32: 2f24bb09 Plugins may alter @actions; ensure action is valid prior to calling
@@ -1,6 +1,6 @@
Author: Alex Vandiver <alexmv at bestpractical.com>
- It is possible that plugins will alter @actions; ensure that we have a valid action before calling it
+ 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
--- a/lib/RT/Interface/Email.pm
33: 82c43c3 = 33: a6838f4 Split default authentication from default authorization
34: 3430b59 ! 34: 4651652 Local'ize MailErrors to avoid having to pass $ErrorsTo everywhere
@@ -138,11 +138,11 @@
@@
my %entity_args = (
Type => "multipart/mixed",
- From => $args{'From'},
-- Bcc => $args{'Bcc'},
- To => $args{'To'},
- Subject => $args{'Subject'},
- 'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
+ 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
--- a/lib/RT/Interface/Email/Action/Defaults.pm
35: a3b032a = 35: 3539367 Remove the warning about the deprecated Auth::GnuPG/Auth::SMIME plugins
36: 8127768 = 36: 384caa6 Allow lazy adding of Auth::MailFrom if no other GetCurrentUser plugins exist
37: dae5c31 = 37: b33b8a4 There is no reason to not always enable Auth::Crypt
38: 3c0d075 ! 38: 59f1c3c Make Crypt not an Auth:: plugin, but hardcoded
@@ -27,8 +27,8 @@
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@
- use strict;
use warnings;
+ use 5.010;
+use RT::Interface::Email::Crypt;
use Email::Address;
@@ -66,7 +66,7 @@
-#
-# COPYRIGHT:
-#
--# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
-# <sales at bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
@@ -231,7 +231,7 @@
-
- foreach my $protocol ( @check_protocols ) {
- my @status = grep defined && length,
-- $part->head->get( "X-RT-$protocol-Status" );
+- map Encode::decode( "UTF-8", $_), $part->head->get( "X-RT-$protocol-Status" );
- next unless @status;
-
- push @found, $protocol;
@@ -242,20 +242,20 @@
- }
- if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
- $part->head->replace(
-- 'X-RT-Incoming-Signature' => $_->{UserString}
+- 'X-RT-Incoming-Signature' => Encode::encode( "UTF-8", $_->{UserString} )
- );
- }
- }
- }
-
- $part->head->replace(
-- 'X-RT-Incoming-Encryption' =>
+- 'X-RT-Incoming-Encryption' =>
- $decrypted ? 'Success' : 'Not encrypted'
- );
- }
-
- my %seen;
-- $args{'Message'}->head->replace( 'X-RT-Privacy' => $_ )
+- $args{'Message'}->head->replace( 'X-RT-Privacy' => Encode::encode( "UTF-8", $_ ) )
- foreach grep !$seen{$_}++, @found;
-}
-
@@ -356,7 +356,7 @@
+#
+# COPYRIGHT:
+#
-+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
++# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
@@ -481,7 +481,7 @@
+
+ foreach my $protocol ( @check_protocols ) {
+ my @status = grep defined && length,
-+ $part->head->get( "X-RT-$protocol-Status" );
++ map Encode::decode( "UTF-8", $_), $part->head->get( "X-RT-$protocol-Status" );
+ next unless @status;
+
+ push @found, $protocol;
@@ -492,20 +492,20 @@
+ }
+ if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
+ $part->head->replace(
-+ 'X-RT-Incoming-Signature' => $_->{UserString}
++ 'X-RT-Incoming-Signature' => Encode::encode( "UTF-8", $_->{UserString} )
+ );
+ }
+ }
+ }
+
+ $part->head->replace(
-+ 'X-RT-Incoming-Encryption' =>
++ 'X-RT-Incoming-Encryption' =>
+ $decrypted ? 'Success' : 'Not encrypted'
+ );
+ }
+
+ my %seen;
-+ $args{'Message'}->head->replace( 'X-RT-Privacy' => $_ )
++ $args{'Message'}->head->replace( 'X-RT-Privacy' => Encode::encode( "UTF-8", $_ ) )
+ foreach grep !$seen{$_}++, @found;
+}
+
39: ddd76e6 ! 39: 9af3360 Move RejectOnUnencrypted to being a mail plugin
@@ -26,10 +26,10 @@
be used in outgoing emails. At this moment, only one protocol can be
used to protect outgoing emails.
--Set C<RejectOnUnencrypted> to true if all incoming email must be
+-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 false if you don't want to reject
+ Set C<RejectOnMissingPrivateKey> to 0 if you don't want to reject
emails encrypted for key RT doesn't have and can not decrypt.
@@
@@ -162,8 +162,8 @@
use strict;
use warnings;
--use RT::Test::SMIME tests => undef, config => 'Set( %Crypt, RejectOnUnencrypted => 1 );';
-+use RT::Test::SMIME tests => undef, config => 'Set( @MailPlugins, "Authz::RequireEncrypted" );';
+-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';
40: 7df59ef ! 40: 65d32d4 Merge ParseAddressFromHeader and RT::EmailParser->ParseEmailAddress
@@ -4,7 +4,7 @@
RT::Interface::Email's ParseAddressFromHeader was merely a thin shim
around RT::EmailParser->ParseEmailAddress, with the tiny addition of
- removing doubled quotes -- which, addording to d5b74f19, had been seen
+ 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.
@@ -36,7 +36,7 @@
- #Figure out who's sending this message.
- foreach my $header ( @sender_headers ) {
+ foreach my $header ( 'Reply-To', 'From', 'Sender' ) {
- my $addr_line = $head->get($header) || next;
+ my $addr_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;
@@ -52,11 +52,11 @@
- #Figure out who's sending this message.
-
foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
-+ my $value = $head->get($header);
++ my $value = Encode::decode( "UTF-8", $head->get($header) );
+ next unless $value;
- # If there's a header of that name
-- my $headerobj = $head->get($header);
+- my $headerobj = Encode::decode( "UTF-8", $head->get($header) );
- if ($headerobj) {
- my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
-
@@ -116,10 +116,10 @@
use strict;
use warnings;
--use RT::Test nodb => 1, tests => 10;
+-use RT::Test tests => 11;
+use RT::Test nodb => 1, tests => undef;
- RT->Config->Set( RTAddressRegexp => qr/^rt\@example.com$/i );
+ ok(require RT::EmailParser);
@@
ok(eq_array(RT::EmailParser->CullRTAddresses(@before), at after), "CullRTAddresses only culls RT addresses");
41: 0910b6f ! 41: 8c71c0f Fix callsites of ParseSenderAddressFromHead to be slightly less incomprehensible
@@ -11,7 +11,7 @@
# the RT Owner. Or maybe all Privileged watchers.
- my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
+ my ( $Sender ) = ParseSenderAddressFromHead($head);
- $head->replace( 'RT-Squelch-Replies-To', $Sender );
+ $head->replace( 'RT-Squelch-Replies-To', Encode::encode("UTF-8", $Sender ) );
$head->replace( 'RT-DetectedAutoGenerated', 'true' );
@@
42: 6b3c173 ! 42: b735284 $MessageId is only used in IsMachineGeneratedMail; move it in there
@@ -10,14 +10,14 @@
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@
- my $From = $head->get("From");
+ my $From = Encode::decode( "UTF-8", $head->get("From") );
chomp $From if defined $From;
-- my $MessageId = $head->get('Message-ID')
+- 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 = $head->get('Subject') || '';
+ my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || '');
chomp $Subject;
@@
if IsMachineGeneratedMail(
@@ -40,7 +40,7 @@
# Warn someone if it's a loop, before we drop it on the ground
if ($IsALoop) {
- $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
-+ my $MessageId = $head->get('Message-ID');
++ 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?
43: 48a5394 ! 43: f71f257 Merge CheckForSuspiciousSender, CheckForAutoGenerated, and CheckForBounce
@@ -71,18 +71,19 @@
-
=head3 CheckForAutoGenerated HEAD
- Takes a HEAD object of L<MIME::Head> class and returns true if message
--is autogenerated. Checks 'Precedence' and 'X-FC-Machinegenerated'
--fields of the head in tests.
-+is autogenerated. This includes bounces, RFC3834 C<Auto-Submitted>
-+headers, as well as heuristics including C<Precedence> and
-+C<X-FC-Machinegenerated> headers.
+ 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
-
+@@
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 =~ /<>/;
@@ -94,11 +95,7 @@
+ return 1 if defined $From and $From eq "";
+
+ # Bulk or junk messages are auto-generated
- my $Precedence = $head->get("Precedence") || "";
-- if ( $Precedence =~ /^(bulk|junk)/i ) {
-- return (1);
-- }
-+ return 1 if $Precedence =~ /^(bulk|junk)/i;
++ 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.
@@ -116,11 +113,12 @@
-
- return (0);
-}
++ return 1 if $FCJunk =~ /^true/i;
+
-
-sub CheckForBounce {
- my $head = shift;
-+ return 1 if $FCJunk =~ /^true/i;
-
+-
- my $ReturnPath = $head->get("Return-path") || "";
- return ( $ReturnPath =~ /<>/ );
+ return 0;
44: e211446 ! 44: b1ea355 Reduce repetition by making MailError handle throwing the FAILURE, as well
@@ -103,7 +103,7 @@
- FAILURE( $error );
}
- my $From = $args{Message}->head->get("From");
+ my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
@@
MailError(
Subject => "Ticket not resolved",
@@ -132,7 +132,7 @@
- FAILURE( $error );
}
- my $From = $args{Message}->head->get("From");
+ my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
@@
MailError(
Subject => "Ticket not taken",
45: 2f7826f ! 45: a008d0a Move ACL checking for Take and Resolve into their own plugins
@@ -96,7 +96,7 @@
+ @_,
+ );
+
- my $From = $args{Message}->head->get("From");
+ my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") );
my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{Ticket}->CurrentUser->id );
46: d4dd6ec ! 46: 140d5c8 Update POD for new methods and functionality
@@ -82,14 +82,23 @@
+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
-
--=over 4
++
+Called before the message is decoded or decrypted. Its return value is
+ignored; it is passed the following parameters:
+
@@ -100,18 +109,24 @@
-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.
+
@@ -147,22 +162,23 @@
+
+=back
+
-
--=item CurrentUser
-
--An C<RT::CurrentUser> object
++
++
+=head3 GetCurrentUser
-
--=item AuthStat
++
+This method is called in order on the mail plugins that define it. The
+first method to return a L<RT::CurrentUser> value shortcuts all other
+plugins. It is passed:
+
++=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
@@ -179,25 +195,20 @@
+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
+
+=back
-
--The authentication level returned from the previous plugin.
-
--=item Ticket [OPTIONAL]
++
++
+=head3 CheckACL
-
--The ticket under discussion
++
+Called to determine authorization -- namely, can the current user
+complete the action in question? While RT's standard permission
+controls apply, this allows a better error message, or more limited
+restrictions on the email gateway.
-
--=item Queue [OPTIONAL]
++
+Only the I<first> action (if there are more than one defined) is
+checked, as the process of completing the first action might affect the
+later actions; consider the case of C<take-correspond>, where the
+C<correspond> action might only be avilable to owners.
-
--If we don't already have a ticket id, we need to know which queue we're talking about
++
+Each plugin defining this method is called in turn; as soon as one
+plugin returns true, the rest are short-circuited. Arguments include:
+
@@ -222,6 +233,48 @@
+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
+
@@ -235,52 +288,10 @@
-the correspondent) or one, which is the normal mode of operation.
-Additionally, if C<-1> is returned, then the processing of the plug-ins
-stops immediately and the message is ignored.
-
-+=head3 HandleI<Action>
-+
-+For any given action I<foo>, the presence of a subroutine called
-+C<HandleFoo> signals the ability of the mailgate to handle that action.
-+The first plugin in to define the method is called, and its return value
-+ignored. It is passed:
-+
-+=over
-+
-+=item Message
-+
-+A L<MIME::Entity> object representing the mail. This may be modified by
-+the plugin.
-+
-+=item Subject
-+
-+A string, the original C<Subject> header of the message before it was
-+modified to extract the ticket id.
-+
-+=item CurrentUser
-+
-+A L<RT::CurrentUser> object representing the authenticated user.
-+
-+=item Ticket
-+
-+A L<RT::Ticket>, the ticket (if any) that has been extracted from the
-+subject. If there was no ticket id, this value will be a L<RT::Ticket>
-+object with no C<id>.
-+
-+=item TicketId
-+
-+The value id that was extracted from the subject; this allows a
-+non-existant ticket id to be differentiated from no subject id, as both
-+will present as having an unloaded C<Ticket> argument.
-+
-+=item Queue
-+
-+A L<RT::Queue>, the C<--queue> argument which was passed L<rt-mailgate>.
-+
-+=back
-+
+=head1 SEE ALSO
+
+L<RT::Interface::Email::Role>
-+
+
+=cut
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
47: b020c8f < --: ------- Add pointers to SUCCESS/FAILURE/TMPFAIL in "writing mail plugins" docs
48: bb657e9 = 47: ac37116 simple test for multiple reply-to addresses
49: 395e0ca ! 48: b78d166 take into account multiple sender's addresses
@@ -133,7 +133,7 @@
+ my @addr;
foreach my $header ( 'Reply-To', 'From', 'Sender' ) {
- my $addr_line = $head->get($header) || next;
+ 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 );
More information about the rt-commit
mailing list