import rt 3.4.5
[freeside.git] / rt / lib / RT / Interface / Email.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4 #  
5 # This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
6 #                                          <jesse@bestpractical.com>
7
8 # (Except where explicitly superseded by other copyright notices)
9
10
11 # LICENSE:
12
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26
27
28 # CONTRIBUTION SUBMISSION POLICY:
29
30 # (The following paragraph is not intended to limit the rights granted
31 # to you to modify and distribute this software under the terms of
32 # the GNU General Public License and is only of importance to you if
33 # you choose to contribute your changes and enhancements to the
34 # community by submitting them to Best Practical Solutions, LLC.)
35
36 # By intentionally submitting any modifications, corrections or
37 # derivatives to this work, or any other work intended for use with
38 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
39 # you are the copyright holder for those contributions and you grant
40 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
41 # royalty-free, perpetual, license to use, copy, create derivative
42 # works based on those contributions, and sublicense and distribute
43 # those contributions and any derivatives thereof.
44
45 # END BPS TAGGED BLOCK }}}
46 package RT::Interface::Email;
47
48 use strict;
49 use Mail::Address;
50 use MIME::Entity;
51 use RT::EmailParser;
52 use File::Temp;
53
54 BEGIN {
55     use Exporter ();
56     use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
57     
58     # set the version for version checking
59     $VERSION = do { my @r = (q$Revision: 1.1.1.6 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
60     
61     @ISA         = qw(Exporter);
62     
63     # your exported package globals go here,
64     # as well as any optionally exported functions
65     @EXPORT_OK   = qw(
66               &CreateUser
67               &GetMessageContent
68               &CheckForLoops 
69               &CheckForSuspiciousSender
70               &CheckForAutoGenerated 
71               &CheckForBounce 
72               &MailError 
73               &ParseCcAddressesFromHead
74               &ParseSenderAddressFromHead 
75               &ParseErrorsToAddressFromHead
76               &ParseAddressFromHeader
77               &Gateway);
78
79 }
80
81 =head1 NAME
82
83   RT::Interface::Email - helper functions for parsing email sent to RT
84
85 =head1 SYNOPSIS
86
87   use lib "!!RT_LIB_PATH!!";
88   use lib "!!RT_ETC_PATH!!";
89
90   use RT::Interface::Email  qw(Gateway CreateUser);
91
92 =head1 DESCRIPTION
93
94
95 =begin testing
96
97 ok(require RT::Interface::Email);
98
99 =end testing
100
101
102 =head1 METHODS
103
104 =cut
105
106
107 # {{{ sub CheckForLoops 
108
109 sub CheckForLoops  {
110     my $head = shift;
111     
112     #If this instance of RT sent it our, we don't want to take it in
113     my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
114     chomp ($RTLoop); #remove that newline
115     if ($RTLoop eq "$RT::rtname") {
116         return (1);
117     }
118     
119     # TODO: We might not trap the case where RT instance A sends a mail
120     # to RT instance B which sends a mail to ...
121     return (undef);
122 }
123
124 # }}}
125
126 # {{{ sub CheckForSuspiciousSender
127
128 sub CheckForSuspiciousSender {
129     my $head = shift;
130
131     #if it's from a postmaster or mailer daemon, it's likely a bounce.
132     
133     #TODO: better algorithms needed here - there is no standards for
134     #bounces, so it's very difficult to separate them from anything
135     #else.  At the other hand, the Return-To address is only ment to be
136     #used as an error channel, we might want to put up a separate
137     #Return-To address which is treated differently.
138     
139     #TODO: search through the whole email and find the right Ticket ID.
140
141     my ($From, $junk) = ParseSenderAddressFromHead($head);
142     
143     if (($From =~ /^mailer-daemon\@/i) or
144         ($From =~ /^postmaster\@/i)){
145         return (1);
146         
147     }
148     
149     return (undef);
150
151 }
152
153 # }}}
154
155 # {{{ sub CheckForAutoGenerated
156 sub CheckForAutoGenerated {
157     my $head = shift;
158     
159     my $Precedence = $head->get("Precedence") || "" ;
160     if ($Precedence =~ /^(bulk|junk)/i) {
161         return (1);
162     }
163     
164     # First Class mailer uses this as a clue.
165     my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
166     if ($FCJunk =~ /^true/i) {
167         return (1);
168     }
169
170     return (0);
171 }
172
173 # }}}
174
175 # {{{ sub CheckForBounce
176 sub CheckForBounce {
177     my $head = shift;
178    
179     my $ReturnPath = $head->get("Return-path") || "" ;
180     return ($ReturnPath =~ /<>/);
181 }
182
183 # }}}
184
185 # {{{ IsRTAddress
186
187 =head2 IsRTAddress ADDRESS
188
189 Takes a single parameter, an email address. 
190 Returns true if that address matches the $RTAddressRegexp.  
191 Returns false, otherwise.
192
193 =cut
194
195 sub IsRTAddress {
196     my $address = shift || '';
197
198     # Example: the following rule would tell RT not to Cc 
199     #   "tickets@noc.example.com"
200     if ( defined($RT::RTAddressRegexp) &&
201                        $address =~ /$RT::RTAddressRegexp/i ) {
202         return(1);
203     } else {
204         return (undef);
205     }
206 }
207
208 # }}}
209
210 # {{{ CullRTAddresses
211
212 =head2 CullRTAddresses ARRAY
213
214 Takes a single argument, an array of email addresses.
215 Returns the same array with any IsRTAddress()es weeded out.
216
217 =cut
218
219 sub CullRTAddresses {
220     return (grep { IsRTAddress($_) } @_);
221 }
222
223 # }}}
224
225 # {{{ sub MailError 
226 sub MailError {
227     my %args = (To => $RT::OwnerEmail,
228                 Bcc => undef,
229                 From => $RT::CorrespondAddress,
230                 Subject => 'There has been an error',
231                 Explanation => 'Unexplained error',
232                 MIMEObj => undef,
233         Attach => undef,
234                 LogLevel => 'crit',
235                 @_);
236
237
238     $RT::Logger->log(level => $args{'LogLevel'}, 
239                      message => $args{'Explanation'}
240                     );
241     my $entity = MIME::Entity->build( Type  =>"multipart/mixed",
242                                       From => $args{'From'},
243                                       Bcc => $args{'Bcc'},
244                                       To => $args{'To'},
245                                       Subject => $args{'Subject'},
246                                       Precedence => 'bulk',
247                                       'X-RT-Loop-Prevention' => $RT::rtname,
248                                     );
249
250     $entity->attach(  Data => $args{'Explanation'}."\n");
251     
252     my $mimeobj = $args{'MIMEObj'};
253     if ($mimeobj) {
254         $mimeobj->sync_headers();
255         $entity->add_part($mimeobj);
256     }
257    
258     if ($args{'Attach'}) {
259         $entity->attach(Data => $args{'Attach'}, Type => 'message/rfc822');
260
261     }
262
263     if ($RT::MailCommand eq 'sendmailpipe') {
264         open (MAIL, "|$RT::SendmailPath $RT::SendmailBounceArguments $RT::SendmailArguments") || return(0);
265         print MAIL $entity->as_string;
266         close(MAIL);
267     }
268     else {
269         $entity->send($RT::MailCommand, $RT::MailParams);
270     }
271 }
272
273 # }}}
274
275 # {{{ Create User
276
277 sub CreateUser {
278     my ($Username, $Address, $Name, $ErrorsTo, $entity) = @_;
279     my $NewUser = RT::User->new($RT::SystemUser);
280
281     my ($Val, $Message) = 
282       $NewUser->Create(Name => ($Username || $Address),
283                        EmailAddress => $Address,
284                        RealName => $Name,
285                        Password => undef,
286                        Privileged => 0,
287                        Comments => 'Autocreated on ticket submission'
288                       );
289     
290     unless ($Val) {
291         
292         # Deal with the race condition of two account creations at once
293         #
294         if ($Username) {
295             $NewUser->LoadByName($Username);
296         }
297         
298         unless ($NewUser->Id) {
299             $NewUser->LoadByEmail($Address);
300         }
301         
302         unless ($NewUser->Id) {  
303             MailError( To => $ErrorsTo,
304                        Subject => "User could not be created",
305                        Explanation => "User creation failed in mailgateway: $Message",
306                        MIMEObj => $entity,
307                        LogLevel => 'crit'
308                      );
309         }
310     }
311
312     #Load the new user object
313     my $CurrentUser = RT::CurrentUser->new();
314     $CurrentUser->LoadByEmail($Address);
315
316     unless ($CurrentUser->id) {
317             $RT::Logger->warning("Couldn't load user '$Address'.".  "giving up");
318                 MailError( To => $ErrorsTo,
319                            Subject => "User could not be loaded",
320                            Explanation => "User  '$Address' could not be loaded in the mail gateway",
321                            MIMEObj => $entity,
322                            LogLevel => 'crit'
323                      );
324     }
325
326     return $CurrentUser;
327 }
328 # }}}
329
330 # {{{ ParseCcAddressesFromHead 
331
332 =head2 ParseCcAddressesFromHead HASHREF
333
334 Takes a hashref object containing QueueObj, Head and CurrentUser objects.
335 Returns a list of all email addresses in the To and Cc 
336 headers b<except> the current Queue\'s email addresses, the CurrentUser\'s 
337 email address  and anything that the configuration sub RT::IsRTAddress matches.
338
339 =cut
340   
341 sub ParseCcAddressesFromHead {
342     my %args = ( Head => undef,
343                  QueueObj => undef,
344                  CurrentUser => undef,
345                  @_ );
346     
347     my (@Addresses);
348         
349     my @ToObjs = Mail::Address->parse($args{'Head'}->get('To'));
350     my @CcObjs = Mail::Address->parse($args{'Head'}->get('Cc'));
351     
352     foreach my $AddrObj (@ToObjs, @CcObjs) {
353         my $Address = $AddrObj->address;
354         $Address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress($Address);
355         next if ($args{'CurrentUser'}->EmailAddress =~ /^\Q$Address\E$/i);
356         next if ($args{'QueueObj'}->CorrespondAddress =~ /^\Q$Address\E$/i);
357         next if ($args{'QueueObj'}->CommentAddress =~ /^\Q$Address\E$/i);
358         next if (RT::EmailParser->IsRTAddress($Address));
359         
360         push (@Addresses, $Address);
361     }
362     return (@Addresses);
363 }
364
365
366 # }}}
367
368 # {{{ ParseSenderAdddressFromHead
369
370 =head2 ParseSenderAddressFromHead
371
372 Takes a MIME::Header object. Returns a tuple: (user@host, friendly name) 
373 of the From (evaluated in order of Reply-To:, From:, Sender)
374
375 =cut
376
377 sub ParseSenderAddressFromHead {
378     my $head = shift;
379     #Figure out who's sending this message.
380     my $From = $head->get('Reply-To') || 
381       $head->get('From') || 
382         $head->get('Sender');
383     return (ParseAddressFromHeader($From));
384 }
385 # }}}
386
387 # {{{ ParseErrorsToAdddressFromHead
388
389 =head2 ParseErrorsToAddressFromHead
390
391 Takes a MIME::Header object. Return a single value : user@host
392 of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
393 From:, Sender)
394
395 =cut
396
397 sub ParseErrorsToAddressFromHead {
398     my $head = shift;
399     #Figure out who's sending this message.
400
401     foreach my $header ('Return-path', 'Errors-To' , 'Reply-To', 'From', 'Sender' ) {
402         # If there's a header of that name
403         my $headerobj = $head->get($header);
404         if ($headerobj) {
405                 my ($addr, $name ) = ParseAddressFromHeader($headerobj);
406                 # If it's got actual useful content...
407                 return ($addr) if ($addr);
408         }
409     }
410 }
411 # }}}
412
413 # {{{ ParseAddressFromHeader
414
415 =head2 ParseAddressFromHeader ADDRESS
416
417 Takes an address from $head->get('Line') and returns a tuple: user@host, friendly name
418
419 =cut
420
421
422 sub ParseAddressFromHeader{
423     my $Addr = shift;
424     
425     # Perl 5.8.0 breaks when doing regex matches on utf8
426     Encode::_utf8_off($Addr) if $] == 5.008;
427     my @Addresses = Mail::Address->parse($Addr);
428     
429     my $AddrObj = $Addresses[0];
430
431     unless (ref($AddrObj)) {
432         return(undef,undef);
433     }
434  
435     my $Name =  ($AddrObj->phrase || $AddrObj->comment || $AddrObj->address);
436     
437     #Lets take the from and load a user object.
438     my $Address = $AddrObj->address;
439
440     return ($Address, $Name);
441 }
442 # }}}
443
444 # {{{ sub ParseTicketId 
445
446
447 sub ParseTicketId {
448     my $Subject = shift;
449     my $id;
450
451     my $test_name = $RT::EmailSubjectTagRegex || qr/\Q$RT::rtname\E/i;
452
453     if ( $Subject =~ s/\[$test_name\s+\#(\d+)\s*\]//i ) {
454         my $id = $1;
455         $RT::Logger->debug("Found a ticket ID. It's $id");
456         return ($id);
457     }
458     else {
459         return (undef);
460     }
461 }
462
463 # }}}
464
465
466 =head2 Gateway ARGSREF
467
468
469 Takes parameters:
470
471     action
472     queue
473     message
474
475
476 This performs all the "guts" of the mail rt-mailgate program, and is
477 designed to be called from the web interface with a message, user
478 object, and so on.
479
480 Can also take an optional 'ticket' parameter; this ticket id overrides
481 any ticket id found in the subject.
482
483 Returns:
484
485     An array of:
486     
487     (status code, message, optional ticket object)
488
489     status code is a numeric value.
490
491       for temporary failures, the status code should be -75
492
493       for permanent failures which are handled by RT, the status code 
494       should be 0
495     
496       for succces, the status code should be 1
497
498
499
500 =cut
501
502 sub Gateway {
503     my $argsref = shift;
504
505     my %args = %$argsref;
506
507     # Set some reasonable defaults
508     $args{'action'} ||= 'correspond';
509     $args{'queue'}  ||= '1';
510
511     # Validate the action
512     my ($status, @actions) = IsCorrectAction( $args{'action'} );
513     unless ( $status ) {
514
515         # Can't safely loc this. What object do we loc around?
516         $RT::Logger->crit("Mail gateway called with an invalid action paramenter '".$actions[0]."' for queue '".$args{'queue'}."'");
517
518         return ( -75, "Invalid 'action' parameter", undef );
519     }
520
521     my $parser = RT::EmailParser->new();
522
523     $parser->SmartParseMIMEEntityFromScalar( Message => $args{'message'});
524
525     if (!$parser->Entity()) {
526         MailError(
527             To          => $RT::OwnerEmail,
528             Subject     => "RT Bounce: Unparseable message",
529             Explanation => "RT couldn't process the message below",
530             Attach     => $args{'message'}
531         );
532
533         return(0,"Failed to parse this message. Something is likely badly wrong with the message");
534     }
535
536     my $Message = $parser->Entity();
537     my $head    = $Message->head;
538
539     my ( $CurrentUser, $AuthStat, $error );
540
541     # Initalize AuthStat so comparisons work correctly
542     $AuthStat = -9999999;
543
544     my $ErrorsTo = ParseErrorsToAddressFromHead($head);
545
546     my $MessageId = $head->get('Message-ID')
547       || "<no-message-id-" . time . rand(2000) . "\@.$RT::Organization>";
548
549     #Pull apart the subject line
550     my $Subject = $head->get('Subject') || '';
551     chomp $Subject;
552
553     $args{'ticket'} ||= ParseTicketId($Subject);
554
555     my $SystemTicket;
556     my $Right = 'CreateTicket';
557     if ( $args{'ticket'} ) {
558         $SystemTicket = RT::Ticket->new($RT::SystemUser);
559         $SystemTicket->Load( $args{'ticket'} );
560         # if there's an existing ticket, this must be a reply
561         $Right = 'ReplyToTicket';
562     }
563
564     #Set up a queue object
565     my $SystemQueueObj = RT::Queue->new($RT::SystemUser);
566     $SystemQueueObj->Load( $args{'queue'} );
567
568     # We can safely have no queue of we have a known-good ticket
569     unless ( $args{'ticket'} || $SystemQueueObj->id ) {
570         return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
571     }
572
573     # Authentication Level
574     # -1 - Get out.  this user has been explicitly declined
575     # 0 - User may not do anything (Not used at the moment)
576     # 1 - Normal user
577     # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate
578
579     push @RT::MailPlugins, "Auth::MailFrom" unless @RT::MailPlugins;
580
581     # Since this needs loading, no matter what
582
583     foreach (@RT::MailPlugins) {
584         my $Code;
585         my $NewAuthStat;
586         if ( ref($_) eq "CODE" ) {
587             $Code = $_;
588         }
589         else {
590             $_ = "RT::Interface::Email::".$_ unless $_ =~ /^RT::Interface::Email::/;
591             eval "require $_;";
592             if ($@) {
593                 $RT::Logger->crit("Couldn't load module '$_': $@");
594                 next;
595             }
596             no strict 'refs';
597             if ( !defined( $Code = *{ $_ . "::GetCurrentUser" }{CODE} ) ) {
598                 $RT::Logger->crit("No GetCurrentUser code found in $_ module");
599                 next;
600             }
601         }
602
603         foreach my $action ( @actions ) {
604
605             ( $CurrentUser, $NewAuthStat ) = $Code->(
606                 Message     => $Message,
607                 RawMessageRef => \$args{'message'},
608                 CurrentUser => $CurrentUser,
609                 AuthLevel   => $AuthStat,
610                 Action      => $action,
611                 Ticket      => $SystemTicket,
612                 Queue       => $SystemQueueObj
613             );
614
615
616             # If a module returns a "-1" then we discard the ticket, so.
617             $AuthStat = -1 if $NewAuthStat == -1;
618
619             # You get the highest level of authentication you were assigned.
620             $AuthStat = $NewAuthStat if $NewAuthStat > $AuthStat;
621
622             last if $AuthStat == -1;
623         }
624
625         last if $AuthStat == -1;
626     }
627
628     # {{{ If authentication fails and no new user was created, get out.
629     if ( !$CurrentUser or !$CurrentUser->Id or $AuthStat == -1 ) {
630
631         # If the plugins refused to create one, they lose.
632         unless ( $AuthStat == -1 ) {
633
634             # Notify the RT Admin of the failure.
635             # XXX Should this be configurable?
636             MailError(
637                 To          => $RT::OwnerEmail,
638                 Subject     => "Could not load a valid user",
639                 Explanation => <<EOT,
640 RT could not load a valid user, and RT's configuration does not allow
641 for the creation of a new user for this email ($ErrorsTo).
642
643 You might need to grant 'Everyone' the right '$Right' for the
644 queue @{[$args{'queue'}]}.
645
646 EOT
647                 MIMEObj  => $Message,
648                 LogLevel => 'error'
649             );
650
651             # Also notify the requestor that his request has been dropped.
652             MailError(
653                 To          => $ErrorsTo,
654                 Subject     => "Could not load a valid user",
655                 Explanation => <<EOT,
656 RT could not load a valid user, and RT's configuration does not allow
657 for the creation of a new user for your email.
658
659 EOT
660                 MIMEObj  => $Message,
661                 LogLevel => 'error'
662             );
663         }
664         return ( 0, "Could not load a valid user", undef );
665     }
666
667     # }}}
668
669     # {{{ Lets check for mail loops of various sorts.
670     my $IsBounce = CheckForBounce($head);
671
672     my $IsAutoGenerated = CheckForAutoGenerated($head);
673
674     my $IsSuspiciousSender = CheckForSuspiciousSender($head);
675
676     my $IsALoop = CheckForLoops($head);
677
678     my $SquelchReplies = 0;
679
680     #If the message is autogenerated, we need to know, so we can not
681     # send mail to the sender
682     if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
683         $SquelchReplies = 1;
684         $ErrorsTo       = $RT::OwnerEmail;
685     }
686
687     # }}}
688
689     # {{{ Drop it if it's disallowed
690     if ( $AuthStat == 0 ) {
691         MailError(
692             To          => $ErrorsTo,
693             Subject     => "Permission Denied",
694             Explanation => "You do not have permission to communicate with RT",
695             MIMEObj     => $Message
696         );
697     }
698
699     # }}}
700     # {{{ Warn someone  if it's a loop
701
702     # Warn someone if it's a loop, before we drop it on the ground
703     if ($IsALoop) {
704         $RT::Logger->crit("RT Recieved mail ($MessageId) from itself.");
705
706         #Should we mail it to RTOwner?
707         if ($RT::LoopsToRTOwner) {
708             MailError(
709                 To          => $RT::OwnerEmail,
710                 Subject     => "RT Bounce: $Subject",
711                 Explanation => "RT thinks this message may be a bounce",
712                 MIMEObj     => $Message
713             );
714         }
715
716         #Do we actually want to store it?
717         return ( 0, "Message Bounced", undef ) unless ($RT::StoreLoops);
718     }
719
720     # }}}
721
722     # {{{ Squelch replies if necessary
723     # Don't let the user stuff the RT-Squelch-Replies-To header.
724     if ( $head->get('RT-Squelch-Replies-To') ) {
725         $head->add(
726             'RT-Relocated-Squelch-Replies-To',
727             $head->get('RT-Squelch-Replies-To')
728         );
729         $head->delete('RT-Squelch-Replies-To');
730     }
731
732     if ($SquelchReplies) {
733
734         # Squelch replies to the sender, and also leave a clue to
735         # allow us to squelch ALL outbound messages. This way we
736         # can punt the logic of "what to do when we get a bounce"
737         # to the scrip. We might want to notify nobody. Or just
738         # the RT Owner. Or maybe all Privileged watchers.
739         my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
740         $head->add( 'RT-Squelch-Replies-To', $Sender );
741         $head->add( 'RT-DetectedAutoGenerated', 'true' );
742     }
743
744     # }}}
745
746     my $Ticket = RT::Ticket->new($CurrentUser);
747
748     # {{{ If we don't have a ticket Id, we're creating a new ticket
749     if ( (!$SystemTicket || !$SystemTicket->Id) && 
750            grep /^(comment|correspond)$/, @actions ) {
751
752         # {{{ Create a new ticket
753
754         my @Cc;
755         my @Requestors = ( $CurrentUser->id );
756
757         if ($RT::ParseNewMessageForTicketCcs) {
758             @Cc = ParseCcAddressesFromHead(
759                 Head        => $head,
760                 CurrentUser => $CurrentUser,
761                 QueueObj    => $SystemQueueObj
762             );
763         }
764
765         my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
766             Queue     => $SystemQueueObj->Id,
767             Subject   => $Subject,
768             Requestor => \@Requestors,
769             Cc        => \@Cc,
770             MIMEObj   => $Message
771         );
772         if ( $id == 0 ) {
773             MailError(
774                 To          => $ErrorsTo,
775                 Subject     => "Ticket creation failed",
776                 Explanation => $ErrStr,
777                 MIMEObj     => $Message
778             );
779             $RT::Logger->error("Create failed: $id / $Transaction / $ErrStr ");
780             return ( 0, "Ticket creation failed", $Ticket );
781         }
782         # strip comments&corresponds from the actions we don't need record twice
783         @actions = grep !/^(comment|correspond)$/, @actions;
784         $args{'ticket'} = $id;
785
786         # }}}
787     }
788
789     $Ticket->Load( $args{'ticket'} );
790     unless ( $Ticket->Id ) {
791         my $message = "Could not find a ticket with id " . $args{'ticket'};
792         MailError(
793             To          => $ErrorsTo,
794             Subject     => "Message not recorded",
795             Explanation => $message,
796             MIMEObj     => $Message
797         );
798     
799         return ( 0, $message );
800     }
801
802     # }}}
803     foreach my $action( @actions ) {
804         #   If the action is comment, add a comment.
805         if ( $action =~ /^(comment|correspond)$/i ) {
806             my ( $status, $msg );
807             if ( $action =~ /^correspond$/i ) {
808                 ( $status, $msg ) = $Ticket->Correspond( MIMEObj => $Message );
809             }
810             else {
811                 ( $status, $msg ) = $Ticket->Comment( MIMEObj => $Message );
812             }
813             unless ($status) {
814     
815                 #Warn the sender that we couldn't actually submit the comment.
816                 MailError(
817                     To          => $ErrorsTo,
818                     Subject     => "Message not recorded",
819                     Explanation => $msg,
820                     MIMEObj     => $Message
821                 );
822                 return ( 0, "Message not recorded", $Ticket );
823             }
824         }
825         elsif ($RT::UnsafeEmailCommands && $action =~ /^take$/i ) {
826             my ( $status, $msg ) = $Ticket->SetOwner( $CurrentUser->id );
827             unless ($status) {
828     
829                 #Warn the sender that we couldn't actually submit the comment.
830                 MailError(
831                     To          => $ErrorsTo,
832                     Subject     => "Ticket not taken",
833                     Explanation => $msg,
834                     MIMEObj     => $Message
835                 );
836                 return ( 0, "Ticket not taken", $Ticket );
837             }
838         }
839         elsif ( $RT::UnsafeEmailCommands && $action =~ /^resolve$/i ) {
840             my ( $status, $msg ) = $Ticket->SetStatus( 'resolved' );
841             unless ($status) {
842                 #Warn the sender that we couldn't actually submit the comment.
843                 MailError(
844                     To          => $ErrorsTo,
845                     Subject     => "Ticket not resolved",
846                     Explanation => $msg,
847                     MIMEObj     => $Message
848                 );
849                 return ( 0, "Ticket not resolved", $Ticket );
850             }
851         }
852     
853         else {
854     
855             #Return mail to the sender with an error
856             MailError(
857                 To          => $ErrorsTo,
858                 Subject     => "RT Configuration error",
859                 Explanation => "'"
860                   . $args{'action'}
861                   . "' not a recognized action."
862                   . " Your RT administrator has misconfigured "
863                   . "the mail aliases which invoke RT",
864                 MIMEObj => $Message
865             );
866             $RT::Logger->crit( $args{'action'} . " type unknown for $MessageId" );
867             return (
868                 -75,
869                 "Configuration error: "
870                   . $args{'action'}
871                   . " not a recognized action",
872                 $Ticket
873             );
874     
875         }
876     }
877
878     return ( 1, "Success", $Ticket );
879 }
880
881 sub IsCorrectAction
882 {
883         my $action = shift;
884         my @actions = split /-/, $action;
885         foreach ( @actions ) {
886                 return (0, $_) unless /^(?:comment|correspond|take|resolve)$/;
887         }
888         return (1, @actions);
889 }
890
891
892 eval "require RT::Interface::Email_Vendor";
893 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm});
894 eval "require RT::Interface::Email_Local";
895 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm});
896
897 1;