#!!!PERL!! -w # $Header: /home/cvs/cvsroot/freeside/rt/bin/rt-mailgate,v 1.1 2002-08-12 06:17:07 ivan Exp $ # (c) 1996-2001 Jesse Vincent # This software is redistributable under the terms of the GNU GPL package RT; use strict; use vars qw($VERSION $Handle $Nobody $SystemUser); $VERSION="!!RT_VERSION!!"; use lib "!!RT_LIB_PATH!!"; use lib "!!RT_ETC_PATH!!"; use RT::Interface::Email qw(CleanEnv LoadConfig DBConnect GetCurrentUser GetMessageContent CheckForLoops CheckForSuspiciousSender CheckForAutoGenerated ParseMIMEEntityFromSTDIN ParseTicketId MailError ParseCcAddressesFromHead ParseSenderAddressFromHead ParseErrorsToAddressFromHead ); #Clean out all the nasties from the environment CleanEnv(); #Load etc/config.pm and drop privs LoadConfig(); #Connect to the database and get RT::SystemUser and RT::Nobody loaded DBConnect(); #Drop setgid permissions RT::DropSetGIDPermissions(); use RT::Ticket; use RT::Queue; use MIME::Parser; use File::Temp; use Mail::Address; #Set some sensible defaults my $Queue = 1; my $time = time; my $Action = "correspond"; my ($Verbose, $ReturnTid, $Debug); my ($From, $TicketId, $Subject,$SquelchReplies); # using --owner-from-extension, this will let you set ticket owner on create my $AssignTicketTo = undef; my ($status, $msg); # {{{ parse commandline while (my $flag = shift @ARGV) { if (($flag eq '-v') or ($flag eq '--verbose')) { $Verbose = 1; } if (($flag eq '-t') or ($flag eq '--ticketid')) { $ReturnTid = 1; } if (($flag eq '-d') or ($flag eq '--debug')) { $RT::Logger->debug("Debug mode enabled\n"); $Debug = 1; } if (($flag eq '-q') or ($flag eq '--queue')) { $Queue = shift @ARGV; } if ($flag eq '--ticket-id-from-extension') { $TicketId = $ENV{'EXTENSION'}; } if ($flag eq '--queue-from-extension') { $Queue = $ENV{'EXTENSION'}; } if ($flag eq '--owner-from-extension') { $AssignTicketTo = $ENV{'EXTENSION'}; } if (($flag eq '-a') or ($flag eq '--action')) { $Action = shift @ARGV; } } # }}} # get the current mime entity from stdin my ($entity, $head) = ParseMIMEEntityFromSTDIN(); #Get someone to send runtime errors to; my $ErrorsTo = ParseErrorsToAddressFromHead($head); #Get us a current user object. my $CurrentUser = GetCurrentUser($head, $entity, $ErrorsTo); # We've already performed a warning and sent the mail off to somewhere safe ($RTOwner). # this is _exceedingly_ unlikely but we don't want to keep going if we don't have a current user unless ($CurrentUser->Id) { exit(1); } my $MessageId = $head->get('Message-Id') || ""; #Pull apart the subject line $Subject = $head->get('Subject') || "[no subject]"; chomp $Subject; # Get the ticket ID unless it's already set $TicketId = ParseTicketId($Subject) unless ($TicketId); #Set up a queue object my $QueueObj = RT::Queue->new($CurrentUser); $QueueObj->Load($Queue); unless ($QueueObj->id ) { MailError(To => $RT::OwnerEmail, Subject => "RT Bounce: $Subject", Explanation => "RT couldn't find the queue: $Queue", MIMEObj => $entity); } # {{{ Lets check for mail loops of various sorts. my $IsAutoGenerated = CheckForAutoGenerated($head); my $IsSuspiciousSender = CheckForSuspiciousSender($head); my $IsALoop = CheckForLoops($head); #If the message is autogenerated, we need to know, so we can not # send mail to the sender if ($IsSuspiciousSender || $IsAutoGenerated || $IsALoop) { $SquelchReplies = 1; $ErrorsTo = $RT::OwnerEmail; #TODO: Is what we want to do here really # "Make the requestor cease to get mail from RT"? # This might wreak havoc with vacation-mailing users. # Maybe have a "disabled for bouncing" state that gets # turned off when we get a legit incoming message } # {{{ Warn someone if it's a loop # Warn someone if it's a loop, before we drop it on the ground if ($IsALoop) { $RT::Logger->crit("RT Received mail ($MessageId) from itself."); #Should we mail it to RTOwner? if ($RT::LoopsToRTOwner) { MailError(To => $RT::OwnerEmail, Subject => "RT Bounce: $Subject", Explanation => "RT thinks this message may be a bounce", MIMEObj => $entity); #Do we actually want to store it? exit unless ($RT::StoreLoops); } } # }}} #Don't let the user stuff the RT-Squelch-Replies-To header. if ($head->get('RT-Squelch-Replies-To')) { $head->add('RT-Relocated-Squelch-Replies-To', $head->get('RT-Squelch-Replies-To')); $head->delete('RT-Squelch-Replies-To') } if ($SquelchReplies) { ## TODO: This is a hack. It should be some other way to ## indicate that the transaction should be "silent". my ($Sender, $junk) = ParseSenderAddressFromHead($head); $head->add('RT-Squelch-Replies-To', $Sender); } # }}} # {{{ If we require that the sender be found in an external DB and they're not # forward this message to RTOwner if ($RT::LookupSenderInExternalDatabase && $RT::SenderMustExistInExternalDatabase ) { MailError(To => $RT::OwnerEmail, Subject => "RT Bounce: $Subject", Explanation => "RT couldn't find requestor via its external database lookup", MIMEObj => $entity); } # }}} # {{{ elsif we don't have a ticket Id, we're creating a new ticket elsif (!defined($TicketId)) { # {{{ Create a new ticket if ($Action =~ /correspond/) { # open a new ticket my @Requestors = ($CurrentUser->id); my @Cc; if ($RT::ParseNewMessageForTicketCcs) { @Cc = ParseCcAddressesFromHead(Head => $head, CurrentUser => $CurrentUser, QueueObj => $QueueObj ); } my $Ticket = new RT::Ticket($CurrentUser); my ($id, $Transaction, $ErrStr) = $Ticket->Create ( Queue => $Queue, Subject => $Subject, Owner => $AssignTicketTo, Requestor => \@Requestors, Cc => \@Cc, MIMEObj => $entity ); if ($id == 0 ) { MailError( To => $ErrorsTo, Subject => "Ticket creation failed", Explanation => $ErrStr, MIMEObj => $entity ); $RT::Logger->error("Create failed: $id / $Transaction / $ErrStr "); } } # }}} else { #TODO Return an error message MailError( To => $ErrorsTo, Subject => "No ticket id specified", Explanation => "$Action aliases require a TicketId to work on", MIMEObj => $entity ); $RT::Logger->crit("$Action aliases require a TicketId to work on ". "(from ".$CurrentUser->UserObj->EmailAddress.") ". $MessageId); } } # }}} # {{{ If we've got a ticket ID, update the ticket else { # If the action is comment, add a comment. if ($Action =~ /comment/i){ my $Ticket = new RT::Ticket($CurrentUser); $Ticket->Load($TicketId); unless ($Ticket->Id) { MailError( To => $ErrorsTo, Subject => "Comment not recorded", Explanation => "Could not find a ticket with id $TicketId", MIMEObj => $entity ); #Return an error message saying that Ticket "#foo" wasn't found. } ($status, $msg) = $Ticket->Comment(MIMEObj=>$entity); unless ($status) { #Warn the sender that we couldn't actually submit the comment. MailError( To => $ErrorsTo, Subject => "Comment not recorded", Explanation => $msg, MIMEObj => $entity ); } } # If the message is correspondence, add it to the ticket elsif ($Action =~ /correspond/i) { my $Ticket = RT::Ticket->new($CurrentUser); $Ticket->Load($TicketId); #TODO: Check for error conditions ($status, $msg) = $Ticket->Correspond(MIMEObj => $entity); unless ($status) { #Return mail to the sender with an error MailError( To => $ErrorsTo, Subject => "Correspondence not recorded", Explanation => $msg, MIMEObj => $entity ); } } else { #Return mail to the sender with an error MailError( To => $ErrorsTo, Subject => "RT Configuration error", Explanation => "'$Action' not a recognized action.". " Your RT administrator has misconfigured ". "the mail aliases which invoke RT" , MIMEObj => $entity ); $RT::Logger->crit("$Action type unknown for $MessageId"); } } # }}} $RT::Handle->Disconnect(); # Everything below this line is a helper sub. most of them will eventually # move to Interface::Email #When we call die, trap it and log->crit with the value of the die. $SIG{__DIE__} = sub { unless ($^S || !defined $^S ) { $RT::Logger->crit("$_[0]"); MailError( To => $ErrorsTo, Bcc => $RT::OwnerEmail, Subject => "RT Critical error. Message not recorded!", Explanation => "$_[0]", MIMEObj => $entity ); exit(-1); } else { #Get out of here if we're in an eval die $_[0]; } }; 1;