#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
use File::Temp;
use UNIVERSAL::require;
use Mail::Mailer ();
+use Text::ParseWords qw/shellwords/;
BEGIN {
use base 'Exporter';
=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,
}
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;
}
if ( $mail_command eq 'sendmailpipe' ) {
my $path = RT->Config->Get('SendmailPath');
- my $args = RT->Config->Get('SendmailArguments');
+ my @args = shellwords(RT->Config->Get('SendmailArguments'));
# SetOutgoingMailFrom
if ( RT->Config->Get('SetOutgoingMailFrom') ) {
$OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
- $args .= " -f $OutgoingMailAddress"
+ push @args, "-f", $OutgoingMailAddress
if $OutgoingMailAddress;
}
# Set Bounce Arguments
- $args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
+ push @args, shellwords(RT->Config->Get('SendmailBounceArguments'))
+ if $args{'Bounce'};
# VERP
if ( $TransactionObj and
my $from = $TransactionObj->CreatorObj->EmailAddress;
$from =~ s/@/=/g;
$from =~ s/\s//g;
- $args .= " -f $prefix$from\@$domain";
+ push @args, "-f", "$prefix$from\@$domain";
}
eval {
# don't ignore CHLD signal to get proper exit code
local $SIG{'CHLD'} = 'DEFAULT';
- open( my $mail, '|-', "$path $args >/dev/null" )
- or die "couldn't execute program: $!";
-
# if something wrong with $mail->print we will get PIPE signal, handle it
local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
+
+ # Make it look to open2 like STDIN is on FD 0, like it
+ # should be; this is necessary because under mod_perl with
+ # the perl-script handler, it's not. This causes our
+ # child's "STDIN" (FD 10-ish) to be set to the pipe we want,
+ # but FD 0 (which the exec'd sendmail assumes is STDIN) is
+ # still open to /dev/null; this ends disasterously.
+ local *STDIN = IO::Handle->new_from_fd( 0, "r" );
+
+ require IPC::Open2;
+ my ($mail, $stdout);
+ my $pid = IPC::Open2::open2( $stdout, $mail, $path, @args )
+ or die "couldn't execute program: $!";
+
$args{'Entity'}->print($mail);
+ close $mail or die "close pipe failed: $!";
- unless ( close $mail ) {
- die "close pipe failed: $!" if $!; # system error
+ waitpid($pid, 0);
+ if ($?) {
# sendmail exit statuses mostly errors with data not software
# TODO: status parsing: core dump, exit on signal or EX_*
- my $msg = "$msgid: `$path $args` exitted with code ". ($?>>8);
+ my $msg = "$msgid: `$path @args` exited with code ". ($?>>8);
$msg = ", interrupted by signal ". ($?&127) if $?&127;
$RT::Logger->error( $msg );
die $msg;
}
};
if ( $@ ) {
- $RT::Logger->crit( "$msgid: Could not send mail with command `$path $args`: " . $@ );
+ $RT::Logger->crit( "$msgid: Could not send mail with command `$path @args`: " . $@ );
if ( $TicketObj ) {
_RecordSendEmailFailure( $TicketObj );
}
return ($value);
}
- return ($value) unless $value =~ /[^\x20-\x7e]/;
+ return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s;
$value =~ s/\s+$//;
my $user = $args{'CurrentUser'}->UserObj;
return
- grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ),
+ grep { $_ ne $current_address
+ && !RT::EmailParser->IsRTAddress( $_ )
+ && !IgnoreCcAddress( $_ )
+ }
map lc $user->CanonicalizeEmailAddress( $_->address ),
map Email::Address->parse( $args{'Head'}->get( $_ ) ),
qw(To Cc);
}
+=head2 IgnoreCcAddress ADDRESS
+
+Returns true if ADDRESS matches the $IgnoreCcRegexp config variable.
+=cut
+
+sub IgnoreCcAddress {
+ my $address = shift;
+ if ( my $address_re = RT->Config->Get('IgnoreCcRegexp') ) {
+ return 1 if $address =~ /$address_re/i;
+ }
+ return undef;
+}
=head2 ParseSenderAddressFromHead HEAD