summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Mailer.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Mailer.pm')
-rw-r--r--Bugzilla/Mailer.pm450
1 files changed, 228 insertions, 222 deletions
diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm
index c9a458b47..9a2023a8c 100644
--- a/Bugzilla/Mailer.pm
+++ b/Bugzilla/Mailer.pm
@@ -32,259 +32,265 @@ use Try::Tiny;
# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
# to disable these warnings.
BEGIN {
- $Return::Value::NO_CLUCK = 1;
+ $Return::Value::NO_CLUCK = 1;
}
use Email::Send;
use Sys::Hostname;
use Bugzilla::Version qw(vers_cmp);
sub MessageToMTA {
- my ($msg, $send_now) = (@_);
- my $method = Bugzilla->get_param_with_override('mail_delivery_method');
- return if $method eq 'None';
-
- if (Bugzilla->get_param_with_override('use_mailer_queue') and !$send_now) {
- Bugzilla->job_queue->insert('send_mail', { msg => $msg });
- return;
+ my ($msg, $send_now) = (@_);
+ my $method = Bugzilla->get_param_with_override('mail_delivery_method');
+ return if $method eq 'None';
+
+ if (Bugzilla->get_param_with_override('use_mailer_queue') and !$send_now) {
+ Bugzilla->job_queue->insert('send_mail', {msg => $msg});
+ return;
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ my $email;
+ if (ref $msg) {
+ $email = $msg;
+ }
+ else {
+ # RFC 2822 requires us to have CRLF for our line endings and
+ # Email::MIME doesn't do this for us until 1.911. We use \015 (CR) and \012 (LF)
+ # directly because Perl translates "\n" depending on what platform
+ # you're running on. See http://perldoc.perl.org/perlport.html#Newlines
+ # We check for multiple CRs because of this Template-Toolkit bug:
+ # https://rt.cpan.org/Ticket/Display.html?id=43345
+ if (vers_cmp($Email::MIME::VERSION, 1.911) == -1) {
+ $msg =~ s/(?:\015+)?\012/\015\012/msg;
}
- my $dbh = Bugzilla->dbh;
+ $email = Email::MIME->new($msg);
+ }
- my $email;
- if (ref $msg) {
- $email = $msg;
- }
- else {
- # RFC 2822 requires us to have CRLF for our line endings and
- # Email::MIME doesn't do this for us until 1.911. We use \015 (CR) and \012 (LF)
- # directly because Perl translates "\n" depending on what platform
- # you're running on. See http://perldoc.perl.org/perlport.html#Newlines
- # We check for multiple CRs because of this Template-Toolkit bug:
- # https://rt.cpan.org/Ticket/Display.html?id=43345
- if (vers_cmp($Email::MIME::VERSION, 1.911) == -1) {
- $msg =~ s/(?:\015+)?\012/\015\012/msg;
- }
-
- $email = Email::MIME->new($msg);
- }
+ # Ensure that we are not sending emails too quickly to recipients.
+ if (Bugzilla->get_param_with_override('use_mailer_queue')
+ && (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR))
+ {
+ $dbh->do("DELETE FROM email_rates WHERE message_ts < "
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'HOUR'));
- # Ensure that we are not sending emails too quickly to recipients.
- if (Bugzilla->get_param_with_override('use_mailer_queue')
- && (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR))
- {
- $dbh->do(
- "DELETE FROM email_rates WHERE message_ts < "
- . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'HOUR'));
+ my $recipient = $email->header('To');
- my $recipient = $email->header('To');
-
- if (EMAIL_LIMIT_PER_MINUTE) {
- my $minute_rate = $dbh->selectrow_array(
- "SELECT COUNT(*)
+ if (EMAIL_LIMIT_PER_MINUTE) {
+ my $minute_rate = $dbh->selectrow_array(
+ "SELECT COUNT(*)
FROM email_rates
WHERE recipient = ? AND message_ts >= "
- . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'MINUTE'),
- undef,
- $recipient);
- if ($minute_rate >= EMAIL_LIMIT_PER_MINUTE) {
- die EMAIL_LIMIT_EXCEPTION;
- }
- }
- if (EMAIL_LIMIT_PER_HOUR) {
- my $hour_rate = $dbh->selectrow_array(
- "SELECT COUNT(*)
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'MINUTE'), undef,
+ $recipient
+ );
+ if ($minute_rate >= EMAIL_LIMIT_PER_MINUTE) {
+ die EMAIL_LIMIT_EXCEPTION;
+ }
+ }
+ if (EMAIL_LIMIT_PER_HOUR) {
+ my $hour_rate = $dbh->selectrow_array(
+ "SELECT COUNT(*)
FROM email_rates
WHERE recipient = ? AND message_ts >= "
- . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'HOUR'),
- undef,
- $recipient);
- if ($hour_rate >= EMAIL_LIMIT_PER_HOUR) {
- die EMAIL_LIMIT_EXCEPTION;
- }
- }
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'HOUR'), undef,
+ $recipient
+ );
+ if ($hour_rate >= EMAIL_LIMIT_PER_HOUR) {
+ die EMAIL_LIMIT_EXCEPTION;
+ }
}
+ }
- # We add this header to uniquely identify all email that we
- # send as coming from this Bugzilla installation.
- #
- $email->header_set('X-Bugzilla-URL', Bugzilla->localconfig->{urlbase});
-
- # We add this header to mark the mail as "auto-generated" and
- # thus to hopefully avoid auto replies.
- $email->header_set('Auto-Submitted', 'auto-generated');
-
- # MIME-Version must be set otherwise some mailsystems ignore the charset
- $email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
-
- # Encode the headers correctly in quoted-printable
- foreach my $header ($email->header_names) {
- my @values = $email->header($header);
- # We don't recode headers that happen multiple times.
- next if scalar(@values) > 1;
- if (my $value = $values[0]) {
- if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($value)) {
- utf8::decode($value);
- }
-
- # avoid excessive line wrapping done by Encode.
- local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;
-
- my $encoded = encode('MIME-Q', $value);
- $email->header_set($header, $encoded);
- }
- }
+ # We add this header to uniquely identify all email that we
+ # send as coming from this Bugzilla installation.
+ #
+ $email->header_set('X-Bugzilla-URL', Bugzilla->localconfig->{urlbase});
- my $from = $email->header('From');
+ # We add this header to mark the mail as "auto-generated" and
+ # thus to hopefully avoid auto replies.
+ $email->header_set('Auto-Submitted', 'auto-generated');
- my ($hostname, @args);
- my $mailer_class = $method;
- if ($method eq "Sendmail") {
- $mailer_class = 'Bugzilla::Send::Sendmail';
- if (ON_WINDOWS) {
- $Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE;
- }
- push @args, "-i";
- # We want to make sure that we pass *only* an email address.
- if ($from) {
- my ($email_obj) = Email::Address->parse($from);
- if ($email_obj) {
- my $from_email = $email_obj->address;
- push(@args, "-f$from_email") if $from_email;
- }
- }
- }
- else {
- # Sendmail will automatically append our hostname to the From
- # address, but other mailers won't.
- my $urlbase = Bugzilla->localconfig->{urlbase};
- $urlbase =~ m|//([^:/]+)[:/]?|;
- $hostname = $1;
- $from .= "\@$hostname" if $from !~ /@/;
- $email->header_set('From', $from);
-
- # Sendmail adds a Date: header also, but others may not.
- if (!defined $email->header('Date')) {
- $email->header_set('Date', time2str("%a, %d %b %Y %T %z", time()));
- }
- }
+ # MIME-Version must be set otherwise some mailsystems ignore the charset
+ $email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
- # For tracking/diagnostic purposes, add our hostname
- my $generated_by = $email->header('X-Generated-By') || '';
- if ($generated_by =~ tr/\/// < 3) {
- $email->header_set('X-Generated-By' => $generated_by . '/' . hostname() . "($$)");
- }
+ # Encode the headers correctly in quoted-printable
+ foreach my $header ($email->header_names) {
+ my @values = $email->header($header);
- if ($method eq "SMTP") {
- push @args, Host => Bugzilla->params->{"smtpserver"},
- username => Bugzilla->params->{"smtp_username"},
- password => Bugzilla->params->{"smtp_password"},
- Hello => $hostname,
- Debug => Bugzilla->params->{'smtp_debug'};
- }
+ # We don't recode headers that happen multiple times.
+ next if scalar(@values) > 1;
+ if (my $value = $values[0]) {
+ if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($value)) {
+ utf8::decode($value);
+ }
- Bugzilla::Hook::process('mailer_before_send',
- { email => $email, mailer_args => \@args });
-
- try {
- my $to = $email->header('to') or die qq{Unable to find "To:" address\n};
- my @recipients = Email::Address->parse($to);
- die qq{Unable to parse "To:" address - $to\n} unless @recipients;
- die qq{Did not expect more than one "To:" address in $to\n} if @recipients > 1;
- my $recipient = $recipients[0];
- my $badhosts = Bugzilla::Bloomfilter->lookup("badhosts");
- if ($badhosts && $badhosts->test($recipient->host)) {
- WARN("Attempted to send email to address in badhosts: $to");
- $email->header_set(to => '');
- }
- elsif ($recipient->host =~ /\.(?:bugs|tld)$/) {
- WARN("Attempted to send email to fake address: $to");
- $email->header_set(to => '');
- }
- } catch {
- ERROR($_);
- };
-
- # Allow for extensions to to drop the bugmail by clearing the 'to' header
- return if $email->header('to') eq '';
-
- $email->walk_parts(sub {
- my ($part) = @_;
- return if $part->parts > 1; # Top-level
- my $content_type = $part->content_type || '';
- $content_type =~ /charset=['"](.+)['"]/;
- # If no charset is defined or is the default us-ascii,
- # then we encode the email to UTF-8 if Bugzilla has utf8 enabled.
- # XXX - This is a hack to workaround bug 723944.
- if (!$1 || $1 eq 'us-ascii') {
- my $body = $part->body;
- if (Bugzilla->params->{'utf8'}) {
- $part->charset_set('UTF-8');
- # encoding_set works only with bytes, not with utf8 strings.
- my $raw = $part->body_raw;
- if (utf8::is_utf8($raw)) {
- utf8::encode($raw);
- $part->body_set($raw);
- }
- }
- $part->encoding_set('quoted-printable') if !is_7bit_clean($body);
- }
- });
-
- if ($method eq "Test") {
- my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
- open TESTFILE, '>>', $filename;
- # From - <date> is required to be a valid mbox file.
- print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
- close TESTFILE;
- }
- else {
- # This is useful for both Sendmail and Qmail, so we put it out here.
- local $ENV{PATH} = SENDMAIL_PATH;
- my $mailer = Email::Send->new({ mailer => $mailer_class,
- mailer_args => \@args });
- my $retval = $mailer->send($email);
- ThrowCodeError('mail_send_error', { msg => $retval, mail => $email })
- if !$retval;
- }
+ # avoid excessive line wrapping done by Encode.
+ local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;
- # insert into email_rates
- if (Bugzilla->get_param_with_override('use_mailer_queue')
- && (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR))
- {
- $dbh->do(
- "INSERT INTO email_rates(recipient, message_ts) VALUES (?, LOCALTIMESTAMP(0))",
- undef,
- $email->header('To')
- );
+ my $encoded = encode('MIME-Q', $value);
+ $email->header_set($header, $encoded);
}
-}
+ }
-# Builds header suitable for use as a threading marker in email notifications
-sub build_thread_marker {
- my ($bug_id, $user_id, $is_new) = @_;
+ my $from = $email->header('From');
- if (!defined $user_id) {
- $user_id = Bugzilla->user->id;
+ my ($hostname, @args);
+ my $mailer_class = $method;
+ if ($method eq "Sendmail") {
+ $mailer_class = 'Bugzilla::Send::Sendmail';
+ if (ON_WINDOWS) {
+ $Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE;
}
-
- my $sitespec = '@' . Bugzilla->localconfig->{urlbase};
- $sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
- $sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
- if ($2) {
- $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
+ push @args, "-i";
+
+ # We want to make sure that we pass *only* an email address.
+ if ($from) {
+ my ($email_obj) = Email::Address->parse($from);
+ if ($email_obj) {
+ my $from_email = $email_obj->address;
+ push(@args, "-f$from_email") if $from_email;
+ }
}
-
- my $threadingmarker = "References: <bug-$bug_id-$user_id$sitespec>";
- if ($is_new) {
- $threadingmarker .= "\nMessage-ID: <bug-$bug_id-$user_id$sitespec>";
+ }
+ else {
+ # Sendmail will automatically append our hostname to the From
+ # address, but other mailers won't.
+ my $urlbase = Bugzilla->localconfig->{urlbase};
+ $urlbase =~ m|//([^:/]+)[:/]?|;
+ $hostname = $1;
+ $from .= "\@$hostname" if $from !~ /@/;
+ $email->header_set('From', $from);
+
+ # Sendmail adds a Date: header also, but others may not.
+ if (!defined $email->header('Date')) {
+ $email->header_set('Date', time2str("%a, %d %b %Y %T %z", time()));
+ }
+ }
+
+ # For tracking/diagnostic purposes, add our hostname
+ my $generated_by = $email->header('X-Generated-By') || '';
+ if ($generated_by =~ tr/\/// < 3) {
+ $email->header_set(
+ 'X-Generated-By' => $generated_by . '/' . hostname() . "($$)");
+ }
+
+ if ($method eq "SMTP") {
+ push @args,
+ Host => Bugzilla->params->{"smtpserver"},
+ username => Bugzilla->params->{"smtp_username"},
+ password => Bugzilla->params->{"smtp_password"},
+ Hello => $hostname,
+ Debug => Bugzilla->params->{'smtp_debug'};
+ }
+
+ Bugzilla::Hook::process('mailer_before_send',
+ {email => $email, mailer_args => \@args});
+
+ try {
+ my $to = $email->header('to') or die qq{Unable to find "To:" address\n};
+ my @recipients = Email::Address->parse($to);
+ die qq{Unable to parse "To:" address - $to\n} unless @recipients;
+ die qq{Did not expect more than one "To:" address in $to\n} if @recipients > 1;
+ my $recipient = $recipients[0];
+ my $badhosts = Bugzilla::Bloomfilter->lookup("badhosts");
+ if ($badhosts && $badhosts->test($recipient->host)) {
+ WARN("Attempted to send email to address in badhosts: $to");
+ $email->header_set(to => '');
+ }
+ elsif ($recipient->host =~ /\.(?:bugs|tld)$/) {
+ WARN("Attempted to send email to fake address: $to");
+ $email->header_set(to => '');
}
- else {
- my $rand_bits = generate_random_password(10);
- $threadingmarker .= "\nMessage-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>" .
- "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>";
+ }
+ catch {
+ ERROR($_);
+ };
+
+ # Allow for extensions to to drop the bugmail by clearing the 'to' header
+ return if $email->header('to') eq '';
+
+ $email->walk_parts(sub {
+ my ($part) = @_;
+ return if $part->parts > 1; # Top-level
+ my $content_type = $part->content_type || '';
+ $content_type =~ /charset=['"](.+)['"]/;
+
+ # If no charset is defined or is the default us-ascii,
+ # then we encode the email to UTF-8 if Bugzilla has utf8 enabled.
+ # XXX - This is a hack to workaround bug 723944.
+ if (!$1 || $1 eq 'us-ascii') {
+ my $body = $part->body;
+ if (Bugzilla->params->{'utf8'}) {
+ $part->charset_set('UTF-8');
+
+ # encoding_set works only with bytes, not with utf8 strings.
+ my $raw = $part->body_raw;
+ if (utf8::is_utf8($raw)) {
+ utf8::encode($raw);
+ $part->body_set($raw);
+ }
+ }
+ $part->encoding_set('quoted-printable') if !is_7bit_clean($body);
}
+ });
+
+ if ($method eq "Test") {
+ my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
+ open TESTFILE, '>>', $filename;
+
+ # From - <date> is required to be a valid mbox file.
+ print TESTFILE "\n\nFrom - "
+ . $email->header('Date') . "\n"
+ . $email->as_string;
+ close TESTFILE;
+ }
+ else {
+ # This is useful for both Sendmail and Qmail, so we put it out here.
+ local $ENV{PATH} = SENDMAIL_PATH;
+ my $mailer = Email::Send->new({mailer => $mailer_class, mailer_args => \@args});
+ my $retval = $mailer->send($email);
+ ThrowCodeError('mail_send_error', {msg => $retval, mail => $email}) if !$retval;
+ }
+
+ # insert into email_rates
+ if (Bugzilla->get_param_with_override('use_mailer_queue')
+ && (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR))
+ {
+ $dbh->do(
+ "INSERT INTO email_rates(recipient, message_ts) VALUES (?, LOCALTIMESTAMP(0))",
+ undef, $email->header('To')
+ );
+ }
+}
- return $threadingmarker;
+# Builds header suitable for use as a threading marker in email notifications
+sub build_thread_marker {
+ my ($bug_id, $user_id, $is_new) = @_;
+
+ if (!defined $user_id) {
+ $user_id = Bugzilla->user->id;
+ }
+
+ my $sitespec = '@' . Bugzilla->localconfig->{urlbase};
+ $sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
+ $sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
+ if ($2) {
+ $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
+ }
+
+ my $threadingmarker = "References: <bug-$bug_id-$user_id$sitespec>";
+ if ($is_new) {
+ $threadingmarker .= "\nMessage-ID: <bug-$bug_id-$user_id$sitespec>";
+ }
+ else {
+ my $rand_bits = generate_random_password(10);
+ $threadingmarker .= "\nMessage-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>"
+ . "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>";
+ }
+
+ return $threadingmarker;
}
1;