On Tue, Jul 12, 2016 at 11:06:44PM +0530, Nilesh wrote: > ... > > > > Does the default RT strip that off? Because I'm not seeing in one recent > > > > duplicate created in the manner I described. > > > > > > > Ah it's hidden inside 'show full headers'. This is interesting. Is there > > > some documentation about this header? > > Hi, > > > > You can look at the RFC's, but that was the header that provided the most > > value in reducing duplicate tickets. > > > > Regards, > > Ken > > As this discussion is going on a few duplicate tickets were created in the > system and this definitely looks like a good way to solve it. Did you use > ExtractCustomFields module to get this done? > > I'm thinking of doing it like this: > Run ExtractCustomFields on every correspond to get the Message ID out of the > headers. Then I can search through this field probably using a customized rt- > mailgate. Does that sound good? I don't think there's a way using scrips > because > by the time scrip executes ticket is already created (unless something is > possible in the "On transaction" condition). >
Hi Nilesh, I have attached my current version of ./local/lib/RT/Interface/Email_Local.pm. It includes our modifications gathered from the list recommendations in the function ParseInReplyTo() This patch is against RT 3.8.13+ and has not been updated to RT 4+ but is should be a straight-forward update. To make your own version of Email_Local.pm, start with just the function ParseInReplyTo() and the calling function and add functions from ./lib/RT/Interface/Email.pm until you have a working file. That will be the minimum that you will need. Take a look at http://requesttracker.wikia.com/wiki/Customizing for more details. Let me know if you have any questions. Regards, Ken
package RT::Interface::Email; use strict; no warnings qw(redefine); use Email::Address; use MIME::Entity; use RT::EmailParser; use File::Temp; use UNIVERSAL::require; use Mail::Mailer (); use Text::ParseWords qw/shellwords/; sub ParseTicketId { my $Subject = shift; my $rtname = RT->Config->Get('rtname'); my $test_name = RT->Config->Get('EmailSubjectTagRegex') || qr/\Q$rtname\E/i; my $id; if ( $Subject =~ s/\[$test_name\s+\#(\d+)\s*\]//i ) { $id = $1; } else { foreach my $tag ( RT->System->SubjectTag ) { next unless $Subject =~ s/\[\Q$tag\E\s+\#(\d+)\s*\]//i; $id = $1; last; } } return undef unless $id; $RT::Logger->debug("Found a ticket ID. It's $id"); return $id; } sub ExtractTicketId { my $entity = shift; my $subject = $entity->head->get('Subject') || ''; chomp $subject; return ParseTicketId( $subject ); } sub ParseInReplyTo { my $MessageId = shift; $MessageId =~ s/<(.+)>/$1/; my $id; if ($MessageId ne '') { $RT::Logger->debug("Looking for matching ticket using In-Reply-To: $MessageId"); my $query = "SELECT ObjectId" . " FROM Transactions JOIN Attachments" . " ON Attachments.TransactionId = Transactions.id" . " WHERE ObjectType = 'RT::Ticket'" . " AND MessageId = ?;"; my @result = $RT::Handle->FetchResult($query, $MessageId); if ( $result[0] =~ /^\d+$/ ) { $id = $result[0]; $RT::Logger->debug("Found a ticket ID using In-Reply-To header. It's $id"); } } return undef unless defined($id); return $id; } sub ParseCcAddressesFromHead { my %args = ( Head => undef, QueueObj => undef, CurrentUser => undef, @_ ); my @recipients = map lc $_->address, map Email::Address->parse( $args{'Head'}->get( $_ ) ), qw(To Cc); my @res; foreach my $address ( @recipients ) { $address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress( $address ); next if lc $args{'CurrentUser'}->EmailAddress eq $address; next if lc $args{'QueueObj'}->CorrespondAddress eq $address; next if lc $args{'QueueObj'}->CommentAddress eq $address; next if RT::EmailParser->IsRTAddress( $address ); push @res, $address; } # # Limit the number of Cc addresses that we add to a # ticket during the initial create to minimize damage # to our Email reputation when SPAM slips through DSPAM. $RT::Logger->debug("$#res Ccs"); if ( $#res > 3 ) { my @riceCc; my @nonriceCc; @riceCc = grep /rice.edu/i, @res; @nonriceCc = grep !/rice.edu/i, @res; $RT::Logger->debug("$#riceCc riceCcs, $#nonriceCc nonriceCcs"); if ($#nonriceCc > 1) { @res = (@riceCc, @nonriceCc[0]); } } return @res; } sub Gateway { my $argsref = shift; my %args = ( action => 'correspond', queue => '1', ticket => undef, message => undef, %$argsref ); 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 ); 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'}, ); $skip_plugin{ $class }++; my $Code = do { no strict 'refs'; *{ $class . "::GetCurrentUser" }{CODE}; }; my ($status, $msg) = $Code->( Message => $Message, RawMessageRef => \$args{'message'}, ); next if $status > 0; if ( $status == -2 ) { return (1, $msg, undef); } elsif ( $status == -1 ) { return (0, $msg, undef); } } @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins; $parser->_DecodeBodies; $parser->_PostProcessNewEntity; my $head = $Message->head; my $ErrorsTo = ParseErrorsToAddressFromHead( $head ); my $MessageId = $head->get('Message-ID') || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>'; #Pull apart the subject line my $Subject = $head->get('Subject') || ''; chomp $Subject; # 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 ); } # }}} my $InReplyToMessageId = $head->get('In-Reply-To') || ''; chomp($InReplyToMessageId); $args{'ticket'} ||= ExtractTicketId( $Message ); $args{'ticket'} ||= ParseInReplyTo( $InReplyToMessageId ); # ExtractTicketId may have been overridden, and edited the Subject my $NewSubject = $Message->head->get('Subject'); chomp $NewSubject; $SystemTicket = RT::Ticket->new( RT->SystemUser ); $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ; if ( $SystemTicket->id ) { $Right = 'ReplyToTicket'; } else { $Right = 'CreateTicket'; } #Set up a queue object my $SystemQueueObj = RT::Queue->new( RT->SystemUser ); $SystemQueueObj->Load( $args{'queue'} ); # 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'} ); } 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, "$ErrorsTo tried to submit a message to " . $args{'Queue'} . " without permission.", undef ); } unless ($should_store_machine_generated_message) { return ( 0, $result, undef ); } # 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 ); } 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 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 ); return ( 0, $error ); } $args{'ticket'} = $Ticket->id; } else { return ( 1, "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: $Subject", Explanation => $msg, MIMEObj => $Message ); return ( 0, "Message 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 ); } 1;
--------- RT 4.4 and RTIR Training Sessions https://bestpractical.com/training * Los Angeles - September, 2016