2 # -*- Mode: perl; indent-tabs-mode: nil -*-
4 # The contents of this file are subject to the Mozilla Public
5 # License Version 1.1 (the "License"); you may not use this file
6 # except in compliance with the License. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS
10 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 # implied. See the License for the specific language governing
12 # rights and limitations under the License.
14 # The Original Code is the Bugzilla Inbound Email System.
16 # The Initial Developer of the Original Code is Akamai Technologies, Inc.
17 # Portions created by Akamai are Copyright (C) 2006 Akamai Technologies,
18 # Inc. All Rights Reserved.
20 # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
25 # MTAs may call this script from any directory, but it should always
26 # run from this one so that it can find its modules.
28 require File
::Basename
;
29 chdir(File
::Basename
::dirname
($0));
36 use Email
::Reply
qw(reply);
38 use Email
::MIME
::Attachment
::Stripper
;
39 use Getopt
::Long
qw(:config bundling);
44 use Bugzilla
::Bug
qw(ValidateBugID);
45 use Bugzilla
::Constants
qw(USAGE_MODE_EMAIL);
56 # This is the USENET standard line for beginning a signature block
57 # in a message. RFC-compliant mailers use this.
58 use constant SIGNATURE_DELIMITER
=> '-- ';
60 # $input_email is a global so that it can be used in die_handler.
61 our ($input_email, %switch);
69 debug_print
('Parsing Email');
70 $input_email = Email
::MIME
->new($mail_text);
74 # Email::Address->parse returns an array
75 my ($reporter) = Email
::Address
->parse($input_email->header('From'));
76 $fields{'reporter'} = $reporter->address;
77 my $summary = $input_email->header('Subject');
78 if ($summary =~ /\[Bug (\d+)\](.*)/i) {
79 $fields{'bug_id'} = $1;
83 my ($body, $attachments) = get_body_and_attachments
($input_email);
85 $fields{'attachments'} = $attachments;
88 debug_print
("Body:\n" . $body, 3);
90 $body = remove_leading_blank_lines
($body);
91 my @body_lines = split(/\r?\n/s, $body);
93 # If there are fields specified.
94 if ($body =~ /^\s*@/s) {
96 while (my $line = shift @body_lines) {
97 # If the sig is starting, we want to keep this in the
98 # @body_lines so that we don't keep the sig as part of the
100 if ($line eq SIGNATURE_DELIMITER
) {
101 unshift(@body_lines, $line);
104 # Otherwise, we stop parsing fields on the first blank line.
108 if ($line =~ /^@(\S+)\s*=\s*(.*)\s*/) {
109 $current_field = lc($1);
110 # It's illegal to pass the reporter field as you could
111 # override the "From:" field of the message and bypass
112 # authentication checks, such as PGP.
113 if ($current_field eq 'reporter') {
114 # We reset the $current_field variable to something
115 # post_bug and process_bug will ignore, in case the
116 # attacker splits the reporter field on several lines.
117 $current_field = 'illegal_field';
120 $fields{$current_field} = $2;
123 $fields{$current_field} .= " $line";
129 # The summary line only affects us if we're doing a post_bug.
130 # We have to check it down here because there might have been
131 # a bug_id specified in the body of the email.
132 if (!$fields{'bug_id'} && !$fields{'short_desc'}) {
133 $fields{'short_desc'} = $summary;
137 # Get the description, except the signature.
138 foreach my $line (@body_lines) {
139 last if $line eq SIGNATURE_DELIMITER
;
140 $comment .= "$line\n";
142 $fields{'comment'} = $comment;
144 debug_print
("Parsed Fields:\n" . Dumper
(\
%fields), 2);
150 my ($fields_in) = @_;
151 my %fields = %$fields_in;
153 debug_print
('Posting a new bug...');
155 my $cgi = Bugzilla
->cgi;
156 foreach my $field (keys %fields) {
157 $cgi->param(-name
=> $field, -value
=> $fields{$field});
160 $cgi->param(-name
=> 'inbound_email', -value
=> 1);
162 require 'post_bug.cgi';
166 my ($fields_in) = @_;
168 my %fields = %$fields_in;
170 my $bug_id = $fields{'bug_id'};
171 $fields{'id'} = $bug_id;
172 delete $fields{'bug_id'};
174 debug_print
("Updating Bug $fields{id}...");
176 ValidateBugID
($bug_id);
177 my $bug = new Bugzilla
::Bug
($bug_id);
179 if ($fields{'bug_status'}) {
180 $fields{'knob'} = $fields{'bug_status'};
182 # If no status is given, then we only want to change the resolution.
183 elsif ($fields{'resolution'}) {
184 $fields{'knob'} = 'change_resolution';
185 $fields{'resolution_knob_change_resolution'} = $fields{'resolution'};
187 if ($fields{'dup_id'}) {
188 $fields{'knob'} = 'duplicate';
191 # Move @cc to @newcc as @cc is used by process_bug.cgi to remove
192 # users from the CC list when @removecc is set.
193 $fields{'newcc'} = delete $fields{'cc'} if $fields{'cc'};
195 # Make it possible to remove CCs.
196 if ($fields{'removecc'}) {
197 $fields{'cc'} = [split(',', $fields{'removecc'})];
198 $fields{'removecc'} = 1;
201 my $cgi = Bugzilla
->cgi;
202 foreach my $field (keys %fields) {
203 $cgi->param(-name
=> $field, -value
=> $fields{$field});
205 $cgi->param('longdesclength', scalar $bug->longdescs);
206 $cgi->param('token', issue_hash_token
([$bug->id, $bug->delta_ts]));
208 require 'process_bug.cgi';
211 ######################
212 # Helper Subroutines #
213 ######################
216 my ($str, $level) = @_;
218 print STDERR
"$str\n" if $level <= $switch{'verbose'};
221 sub get_body_and_attachments
{
224 my $ct = $email->content_type || 'text/plain';
225 debug_print
("Splitting Body and Attachments [Type: $ct]...");
228 my $attachments = [];
229 if ($ct =~ /^multipart\/alternative
/i
) {
230 $body = get_text_alternative
($email);
233 my $stripper = new Email
::MIME
::Attachment
::Stripper
(
234 $email, force_filename
=> 1);
235 my $message = $stripper->message;
236 $body = get_text_alternative
($message);
237 $attachments = [$stripper->attachments];
240 return ($body, $attachments);
243 sub get_text_alternative
{
246 my @parts = $email->parts;
248 foreach my $part (@parts) {
249 my $ct = $part->content_type || 'text/plain';
250 my $charset = 'iso-8859-1';
251 # The charset may be quoted.
252 if ($ct =~ /charset="?([^;"]+)/) {
255 debug_print
("Part Content-Type: $ct", 2);
256 debug_print
("Part Character Encoding: $charset", 2);
257 if (!$ct || $ct =~ /^text\/plain
/i
) {
259 if (Bugzilla
->params->{'utf8'} && !utf8
::is_utf8
($body)) {
260 $body = Encode
::decode
($charset, $body);
266 if (!defined $body) {
267 # Note that this only happens if the email does not contain any
268 # text/plain parts. If the email has an empty text/plain part,
269 # you're fine, and this message does NOT get thrown.
270 ThrowUserError
('email_no_text_plain');
276 sub remove_leading_blank_lines
{
278 $text =~ s/^(\s*\n)+//s;
284 # Trivial HTML tag remover (this is just for error messages, really.)
285 $var =~ s/<[^>]*>//g;
286 # And this basically reverses the Template-Toolkit html filter.
287 $var =~ s/\&/\&/g;
290 $var =~ s/\"/\"/g;
292 # Also remove undesired newlines and consecutive spaces.
293 $var =~ s/[\n\s]+/ /gms;
301 # In Template-Toolkit, [% RETURN %] is implemented as a call to "die".
302 # But of course, we really don't want to actually *die* just because
303 # the user-error or code-error template ended. So we don't really die.
304 return if $msg->isa('Template::Exception') && $msg->type eq 'return';
306 # If this is inside an eval, then we should just act like...we're
307 # in an eval (instead of printing the error and exiting).
310 # We can't depend on the MTA to send an error message, so we have
311 # to generate one properly.
313 $msg =~ s/at .+ line.*$//ms;
314 $msg =~ s/^Compilation failed in require.+$//ms;
315 $msg = html_strip
($msg);
316 my $from = Bugzilla
->params->{'mailfrom'};
317 my $reply = reply
(to
=> $input_email, from
=> $from, top_post
=> 1,
319 MessageToMTA
($reply->as_string);
321 print STDERR
"$msg\n";
322 # We exit with a successful value, because we don't want the MTA
323 # to *also* send a failure notice.
331 $SIG{__DIE__
} = \
&die_handler
;
333 GetOptions
(\
%switch, 'help|h', 'verbose|v+');
334 $switch{'verbose'} ||= 0;
336 # Print the help message if that switch was selected.
337 pod2usage
({-verbose
=> 0, -exitval
=> 1}) if $switch{'help'};
339 Bugzilla
->usage_mode(USAGE_MODE_EMAIL
);
342 my @mail_lines = <STDIN
>;
343 my $mail_text = join("", @mail_lines);
344 my $mail_fields = parse_mail
($mail_text);
346 my $username = $mail_fields->{'reporter'};
347 # If emailsuffix is in use, we have to remove it from the email address.
348 if (my $suffix = Bugzilla
->params->{'emailsuffix'}) {
349 $username =~ s/\Q$suffix\E$//i;
352 my $user = Bugzilla
::User
->new({ name
=> $username })
353 || ThrowUserError
('invalid_username', { name
=> $username });
355 Bugzilla
->set_user($user);
357 if ($mail_fields->{'bug_id'}) {
358 process_bug
($mail_fields);
361 post_bug
($mail_fields);
368 email_in.pl - The Bugzilla Inbound Email Interface
372 ./email_in.pl [-vvv] < email.txt
374 Reads an email on STDIN (the standard input).
377 --verbose (-v) - Make the script print more to STDERR.
378 Specify multiple times to print even more.
382 This script processes inbound email and creates a bug, or appends data
385 =head2 Creating a New Bug
387 The script expects to read an email with the following format:
389 From: account@domain.com
392 @product = ProductName
393 @component = ComponentName
396 This is a bug description. It will be entered into the bug exactly as
399 It can be multiple paragraphs.
402 This is a signature line, and will be removed automatically, It will not
403 be included in the bug description.
405 The C<@> labels can be any valid field name in Bugzilla that can be
406 set on C<enter_bug.cgi>. For the list of required field names, see
407 L<Bugzilla::WebService::Bug/Create>. Note, that there is some difference
408 in the names of the required input fields between web and email interfaces,
415 C<platform> in web is C<@rep_platform> in email
419 C<severity> in web is C<@bug_severity> in email
423 For the list of all field names, see the C<fielddefs> table in the database.
425 The values for the fields can be split across multiple lines, but
426 note that a newline will be parsed as a single space, for the value.
429 @short_desc = This is a very long
432 Will be parsed as "This is a very long description".
434 If you specify C<@short_desc>, it will override the summary you specify
435 in the Subject header.
437 C<account@domain.com> must be a valid Bugzilla account.
439 Note that signatures must start with '-- ', the standard signature
442 =head2 Modifying an Existing Bug
444 Bugzilla determines what bug you want to modify in one of two ways:
450 Your subject starts with [Bug 123456] -- then it modifies bug 123456.
454 You include C<@bug_id = 123456> in the first lines of the email.
458 If you do both, C<@bug_id> takes precedence.
460 You send your email in the same format as for creating a bug, except
461 that you only specify the fields you want to change. If the very
462 first non-blank line of the email doesn't begin with C<@>, then it
463 will be assumed that you are only adding a comment to the bug.
465 Note that when updating a bug, the C<Subject> header is ignored,
466 except for getting the bug ID. If you want to change the bug's summary,
467 you have to specify C<@short_desc> as one of the fields to change.
469 Please remember not to include any extra text in your emails, as that
470 text will also be added as a comment. This includes any text that your
471 email client automatically quoted and included, if this is a reply to
474 =head3 Adding/Removing CCs
476 To add CCs, you can specify them in a comma-separated list in C<@cc>.
477 For backward compatibility, C<@newcc> can also be used. If both are
478 present, C<@cc> takes precedence.
480 To remove CCs, specify them as a comma-separated list in C<@removecc>.
484 If your request cannot be completed for any reason, Bugzilla will
485 send an email back to you. If your request succeeds, Bugzilla will
486 not send you anything.
488 If any part of your request fails, all of it will fail. No partial
491 There is no attachment support yet.
495 The script does not do any validation that the user is who they say
496 they are. That is, it accepts I<any> 'From' address, as long as it's
497 a valid Bugzilla account. So make sure that your MTA validates that
498 the message is actually coming from who it says it's coming from,
499 and only allow access to the inbound email system from people you trust.
503 Note that the email interface has the same limitations as the
504 normal Bugzilla interface. So, for example, you cannot reassign
505 a bug and change its status at the same time.
507 The email interface only accepts emails that are correctly formatted
508 perl RFC2822. If you send it an incorrectly formatted message, it
509 may behave in an unpredictable fashion.
511 You cannot send an HTML mail along with attachments. If you do, Bugzilla
512 will reject your email, saying that it doesn't contain any text. This
513 is a bug in L<Email::MIME::Attachment::Stripper> that we can't work
516 You cannot modify Flags through the email interface.