X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FInterface%2FEmail.pm;h=74120ba07bf203ebed13439a964921b59f2df005;hp=02a1ec0c02bc3236d1bd8e4fbcf5b620505a7d60;hb=e9e0cf0989259b94d9758eceff448666a2e5a5cc;hpb=a6fe07e49e3fc12169e801b1ed6874c3a5bd8500 diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm index 02a1ec0c0..74120ba07 100755 --- a/rt/lib/RT/Interface/Email.pm +++ b/rt/lib/RT/Interface/Email.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -149,6 +149,9 @@ sub CheckForSuspiciousSender { my ( $From, $junk ) = ParseSenderAddressFromHead($head); + # If unparseable (non-ASCII), $From can come back undef + return undef if not defined $From; + if ( ( $From =~ /^mailer-daemon\@/i ) or ( $From =~ /^postmaster\@/i ) or ( $From eq "" )) @@ -222,8 +225,8 @@ add 'In-Reply-To' field to the error that points to this message. =item Attach - optional text that attached to the error as 'message/rfc822' part. -=item LogLevel - log level under which we should write explanation message into the -log, by default we log it as critical. +=item LogLevel - log level under which we should write the subject and +explanation message into the log, by default we log it as critical. =back @@ -244,7 +247,7 @@ sub MailError { $RT::Logger->log( level => $args{'LogLevel'}, - message => $args{'Explanation'} + message => "$args{Subject}: $args{'Explanation'}", ) if $args{'LogLevel'}; # the colons are necessary to make ->build include non-standard headers @@ -318,6 +321,35 @@ header field then it's value is used =cut +sub WillSignEncrypt { + my %args = @_; + my $attachment = delete $args{Attachment}; + my $ticket = delete $args{Ticket}; + + if ( not RT->Config->Get('GnuPG')->{'Enable'} ) { + $args{Sign} = $args{Encrypt} = 0; + return wantarray ? %args : 0; + } + + for my $argument ( qw(Sign Encrypt) ) { + next if defined $args{ $argument }; + + if ( $attachment and defined $attachment->GetHeader("X-RT-$argument") ) { + $args{$argument} = $attachment->GetHeader("X-RT-$argument"); + } elsif ( $ticket and $argument eq "Encrypt" ) { + $args{Encrypt} = $ticket->QueueObj->Encrypt(); + } elsif ( $ticket and $argument eq "Sign" ) { + # Note that $queue->Sign is UI-only, and that all + # UI-generated messages explicitly set the X-RT-Crypt header + # to 0 or 1; thus this path is only taken for messages + # generated _not_ via the web UI. + $args{Sign} = $ticket->QueueObj->SignAuto(); + } + } + + return wantarray ? %args : ($args{Sign} || $args{Encrypt}); +} + sub SendEmail { my (%args) = ( Entity => undef, @@ -366,23 +398,12 @@ sub SendEmail { } if ( RT->Config->Get('GnuPG')->{'Enable'} ) { - my %crypt; - - my $attachment; - $attachment = $TransactionObj->Attachments->First - if $TransactionObj; - - foreach my $argument ( qw(Sign Encrypt) ) { - next if defined $args{ $argument }; - - if ( $attachment && defined $attachment->GetHeader("X-RT-$argument") ) { - $crypt{$argument} = $attachment->GetHeader("X-RT-$argument"); - } elsif ( $TicketObj ) { - $crypt{$argument} = $TicketObj->QueueObj->$argument(); - } - } - - my $res = SignEncrypt( %args, %crypt ); + %args = WillSignEncrypt( + %args, + Attachment => $TransactionObj ? $TransactionObj->Attachments->First : undef, + Ticket => $TicketObj, + ); + my $res = SignEncrypt( %args ); return $res unless $res > 0; } @@ -410,21 +431,24 @@ sub SendEmail { # SetOutgoingMailFrom and bounces conflict, since they both want -f if ( $args{'Bounce'} ) { push @args, shellwords(RT->Config->Get('SendmailBounceArguments')); - } elsif ( RT->Config->Get('SetOutgoingMailFrom') ) { - my $OutgoingMailAddress; + } elsif ( my $MailFrom = RT->Config->Get('SetOutgoingMailFrom') ) { + my $OutgoingMailAddress = $MailFrom =~ /\@/ ? $MailFrom : undef; + my $Overrides = RT->Config->Get('OverrideOutgoingMailFrom') || {}; if ($TicketObj) { my $QueueName = $TicketObj->QueueObj->Name; - my $QueueAddressOverride = RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName}; + my $QueueAddressOverride = $Overrides->{$QueueName}; if ($QueueAddressOverride) { $OutgoingMailAddress = $QueueAddressOverride; } else { - $OutgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress; + $OutgoingMailAddress ||= $TicketObj->QueueObj->CorrespondAddress + || RT->Config->Get('CorrespondAddress'); } } - - $OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'}; + elsif ($Overrides->{'Default'}) { + $OutgoingMailAddress = $Overrides->{'Default'}; + } push @args, "-f", $OutgoingMailAddress if $OutgoingMailAddress; @@ -787,7 +811,7 @@ sub GetForwardFrom { my $ticket = $args{Ticket} || $txn->Object; if ( RT->Config->Get('ForwardFromUser') ) { - return ( $txn || $ticket )->CurrentUser->UserObj->EmailAddress; + return ( $txn || $ticket )->CurrentUser->EmailAddress; } else { return $ticket->QueueObj->CorrespondAddress @@ -1041,7 +1065,7 @@ sub CreateUser { Takes a hash containing QueueObj, Head and CurrentUser objects. Returns a list of all email addresses in the To and Cc -headers b the current Queue\'s email addresses, the CurrentUser\'s +headers b the current Queue's email addresses, the CurrentUser's email address and anything that the configuration sub RT::IsRTAddress matches. =cut @@ -1063,7 +1087,7 @@ sub ParseCcAddressesFromHead { && !IgnoreCcAddress( $_ ) } map lc $user->CanonicalizeEmailAddress( $_->address ), - map Email::Address->parse( $args{'Head'}->get( $_ ) ), + map RT::EmailParser->CleanupAddresses( Email::Address->parse( $args{'Head'}->get( $_ ) ) ), qw(To Cc); } @@ -1083,23 +1107,34 @@ sub IgnoreCcAddress { =head2 ParseSenderAddressFromHead HEAD -Takes a MIME::Header object. Returns a tuple: (user@host, friendly name) -of the From (evaluated in order of Reply-To:, From:, Sender) +Takes a MIME::Header object. Returns (user@host, friendly name, errors) +where the first two values are the From (evaluated in order of +Reply-To:, From:, Sender). + +A list of error messages may be returned even when a Sender value is +found, since it could be a parse error for another (checked earlier) +sender field. In this case, the errors aren't fatal, but may be useful +to investigate the parse failure. =cut sub ParseSenderAddressFromHead { my $head = shift; + my @sender_headers = ('Reply-To', 'From', 'Sender'); + my @errors; # Accumulate any errors #Figure out who's sending this message. - foreach my $header ('Reply-To', 'From', 'Sender') { + foreach my $header ( @sender_headers ) { my $addr_line = $head->get($header) || next; my ($addr, $name) = ParseAddressFromHeader( $addr_line ); # only return if the address is not empty - return ($addr, $name) if $addr; + return ($addr, $name, @errors) if $addr; + + chomp $addr_line; + push @errors, "$header: $addr_line"; } - return (undef, undef); + return (undef, undef, @errors); } =head2 ParseErrorsToAddressFromHead HEAD @@ -1221,8 +1256,16 @@ sub SetInReplyTo { if @references > 10; my $mail = $args{'Message'}; - $mail->head->set( 'In-Reply-To' => join ' ', @rtid? (@rtid) : (@id) ) if @id || @rtid; - $mail->head->set( 'References' => join ' ', @references ); + $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) ); +} + +sub ExtractTicketId { + my $entity = shift; + + my $subject = $entity->head->get('Subject') || ''; + chomp $subject; + return ParseTicketId( $subject ); } sub ParseTicketId { @@ -1419,10 +1462,14 @@ sub Gateway { } @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins; $parser->_DecodeBodies; + $parser->RescueOutlook; $parser->_PostProcessNewEntity; my $head = $Message->head; my $ErrorsTo = ParseErrorsToAddressFromHead( $head ); + my $Sender = (ParseSenderAddressFromHead( $head ))[0]; + my $From = $head->get("From"); + chomp $From if defined $From; my $MessageId = $head->get('Message-ID') || "Config->Get('Organization') .'>'; @@ -1448,7 +1495,11 @@ sub Gateway { } # }}} - $args{'ticket'} ||= ParseTicketId( $Subject ); + $args{'ticket'} ||= ExtractTicketId( $Message ); + + # 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'} ) ; @@ -1503,7 +1554,8 @@ sub Gateway { ); return ( 0, - "$ErrorsTo tried to submit a message to " + ($CurrentUser->EmailAddress || $CurrentUser->Name) + . " ($Sender) tried to submit a message to " . $args{'Queue'} . " without permission.", undef @@ -1534,9 +1586,11 @@ sub Gateway { ); } + $head->replace('X-RT-Interface' => 'Email'); + my ( $id, $Transaction, $ErrStr ) = $Ticket->Create( Queue => $SystemQueueObj->Id, - Subject => $Subject, + Subject => $NewSubject, Requestor => \@Requestors, Cc => \@Cc, MIMEObj => $Message @@ -1548,7 +1602,7 @@ sub Gateway { Explanation => $ErrStr, MIMEObj => $Message ); - return ( 0, "Ticket creation failed: $ErrStr", $Ticket ); + return ( 0, "Ticket creation From: $From failed: $ErrStr", $Ticket ); } # strip comments&corresponds from the actions we don't need @@ -1589,11 +1643,11 @@ sub Gateway { #Warn the sender that we couldn't actually submit the comment. MailError( To => $ErrorsTo, - Subject => "Message not recorded: $Subject", + Subject => "Message not recorded ($method): $Subject", Explanation => $msg, MIMEObj => $Message ); - return ( 0, "Message not recorded: $msg", $Ticket ); + return ( 0, "Message From: $From not recorded: $msg", $Ticket ); } } elsif ($unsafe_actions) { my ( $status, $msg ) = _RunUnsafeAction( @@ -1692,6 +1746,8 @@ sub _RunUnsafeAction { @_ ); + my $From = $args{Message}->head->get("From"); + if ( $args{'Action'} =~ /^take$/i ) { my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{'CurrentUser'}->id ); unless ($status) { @@ -1701,23 +1757,26 @@ sub _RunUnsafeAction { Explanation => $msg, MIMEObj => $args{'Message'} ); - return ( 0, "Ticket not taken" ); + return ( 0, "Ticket not taken, by email From: $From" ); } } elsif ( $args{'Action'} =~ /^resolve$/i ) { - my ( $status, $msg ) = $args{'Ticket'}->SetStatus('resolved'); - unless ($status) { + my $new_status = $args{'Ticket'}->FirstInactiveStatus; + if ($new_status) { + my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status); + unless ($status) { - #Warn the sender that we couldn't actually submit the comment. - MailError( - To => $args{'ErrorsTo'}, - Subject => "Ticket not resolved", - Explanation => $msg, - MIMEObj => $args{'Message'} - ); - return ( 0, "Ticket not resolved" ); + #Warn the sender that we couldn't actually submit the comment. + MailError( + To => $args{'ErrorsTo'}, + Subject => "Ticket not resolved", + Explanation => $msg, + MIMEObj => $args{'Message'} + ); + return ( 0, "Ticket not resolved, by email From: $From" ); + } } } else { - return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} ); + return ( 0, "Not supported unsafe action $args{'Action'}, by email From: $From", $args{'Ticket'} ); } return ( 1, "Success" ); }