]> git.donarmstrong.com Git - debbugs.git/blob - scripts/process.in
add default package support
[debbugs.git] / scripts / process.in
1 #!/usr/bin/perl
2 # $Id: process.in,v 1.109 2006/02/09 22:02:04 don Exp $
3 #
4 # Usage: process nn
5 # Temps:  incoming/Pnn
6
7 use warnings;
8 use strict;
9
10 use POSIX qw(strftime);
11
12 use MIME::Parser;
13 use Debbugs::MIME qw(decode_rfc1522 create_mime_message getmailbody);
14 use Debbugs::Mail qw(send_mail_message encode_headers);
15 use Debbugs::Packages qw(getpkgsrc);
16 use Debbugs::User qw(read_usertags write_usertags);
17 use Debbugs::Common qw(:lock get_hashname);
18 use Debbugs::Status qw(writebug isstrongseverity lockreadbugmerge lockreadbug);
19
20 use Debbugs::CGI qw(html_escape bug_url);
21
22 use Debbugs::Log qw(:misc);
23
24 use Debbugs::Text qw(:templates);
25
26 use Debbugs::Status qw(:versions);
27 use Debbugs::Config qw(:globals :config);
28
29 chdir( "$gSpoolDir" ) || die "chdir spool: $!\n";
30
31 #open(DEBUG,"> /tmp/debbugs.debug");
32 umask(002);
33 open DEBUG, ">/dev/null";
34
35 my $intdate = time or quit("failed to get time: $!");
36
37 $_=shift;
38 m/^([BMQFDUL])(\d*)\.\d+$/ or quit("bad argument: $_");
39 my $codeletter= $1;
40 my $tryref= length($2) ? $2 : -1;
41 my $nn= $_;
42
43 if (!rename("incoming/G$nn","incoming/P$nn")) 
44 {
45     $_=$!.'';  m/no such file or directory/i && exit 0;
46     &quit("renaming to lock: $!");
47 }
48
49 my $baddress= 'submit' if $codeletter eq 'B';
50 $baddress= 'maintonly' if $codeletter eq 'M';
51 $baddress= 'quiet' if $codeletter eq 'Q';
52 $baddress= 'forwarded' if $codeletter eq 'F';
53 $baddress= 'done' if $codeletter eq 'D';
54 $baddress= 'submitter' if $codeletter eq 'U';
55 bug_list_forward($nn) if $codeletter eq 'L';
56 $baddress || &quit("bad codeletter $codeletter");
57 my $baddressroot= $baddress;
58 $baddress= "$tryref-$baddress" if $tryref>=0;
59
60 open(M,"incoming/P$nn");
61 my @log=<M>;
62 close(M);
63
64 my @msg = @log;
65 chomp @msg;
66
67 print DEBUG "###\n",join("##\n",@msg),"\n###\n";
68
69 my $tdate = strftime "%a, %d %h %Y %T +0000", gmtime;
70 my $fwd= <<END;
71 Received: via spool by $baddress\@$gEmailDomain id=$nn
72           (code $codeletter ref $tryref); $tdate
73 END
74
75 # header and decoded body respectively
76 my (@headerlines, @bodylines);
77
78 # whether maintainer addresses have been checked
79 our $maintainerschecked = 0;
80 #maintainer address for this message
81 our @maintaddrs;
82 # other src addresses
83 our @addsrcaddrs;
84 our @resentccs;
85 our @bccs;
86
87 my $resentccexplain='';
88
89 # whether there's a new reference with this email
90 our $newref = 0;
91
92 our $brokenness = '';
93
94 my $parser = new MIME::Parser;
95 mkdir "$gSpoolDir/mime.tmp", 0777;
96 $parser->output_under("$gSpoolDir/mime.tmp");
97 my $entity = eval { $parser->parse_data(join('',@log)) };
98
99 my $i;
100 if ($entity and $entity->head->tags) {
101     @headerlines = @{$entity->head->header};
102     chomp @headerlines;
103
104     my $entity_body = getmailbody($entity);
105     @bodylines = $entity_body ? $entity_body->as_lines() : ();
106     chomp @bodylines;
107
108     # set $i to beginning of encoded body data, so we can dump it out
109     # verbatim later
110     $i = 0;
111     ++$i while $msg[$i] =~ /./;
112 } else {
113     # Legacy pre-MIME code, kept around in case MIME::Parser fails.
114     for ($i = 0; $i <= $#msg; $i++) {
115         $_ = $msg[$i];
116         last unless length($_);
117         while ($msg[$i+1] =~ m/^\s/) {
118             $i++;
119             $_ .= "\n".$msg[$i];
120         }
121         push @headerlines, $_;
122     }
123
124     @bodylines = @msg[$i..$#msg];
125 }
126
127 my %header;
128
129 for my $hdr (@headerlines) {
130     $hdr = decode_rfc1522($hdr);
131     $_ = $hdr;
132     s/\n\s/ /g;
133     &finish if m/^x-loop: (\S+)$/i && $1 eq "$gMaintainerEmail";
134     my $ins = !m/^subject:/i && !m/^reply-to:/i && !m/^return-path:/i
135            && !m/^From / && !m/^X-Debbugs-/i;
136     $fwd .= $hdr."\n" if $ins;
137     # print DEBUG ">$_<\n";
138     if (s/^(\S+):\s*//) {
139         my $v = lc $1;
140         print DEBUG ">$v=$_<\n";
141         $header{$v} = $_;
142     } else {
143         print DEBUG "!>$_<\n";
144     }
145 }
146 $header{'message-id'} = '' if not defined $header{'message-id'};
147
148 # remove blank lines
149 shift @bodylines while @bodylines and $bodylines[0] !~ /\S/;
150
151 # Strip off RFC2440-style PGP clearsigning.
152 if (@bodylines and $bodylines[0] =~ /^-----BEGIN PGP SIGNED/) {
153     shift @bodylines while @bodylines and length $bodylines[0];
154     shift @bodylines while @bodylines and $bodylines[0] !~ /\S/;
155     for my $findsig (0 .. $#bodylines) {
156         if ($bodylines[$findsig] =~ /^-----BEGIN PGP SIGNATURE/) {
157             $#bodylines = $findsig - 1;
158             last;
159         }
160     }
161     map { s/^- // } @bodylines;
162 }
163
164 #psuedoheaders
165 my %pheader;
166 # extract pseudo-headers
167 for my $phline (@bodylines)
168 {
169     last if $phline !~ m/^([\w-]+):\s*(\S.*)/;
170     my ($fn, $fv) = ($1, $2);
171     $fv =~ s/\s*$//;
172     print DEBUG ">$fn|$fv|\n";
173     $fn = lc $fn;
174     # Don't lc owner or forwarded
175     $fv = lc $fv unless $fn =~ /^(?:owner|forwarded|usertags|version|source-version)$/;
176     $pheader{$fn} = $fv;
177     print DEBUG ">$fn~$fv<\n";
178 }
179
180 # Allow pseudo headers to set x-debbugs- stuff [#179340]
181 for my $key (grep /X-Debbugs-.*/i, keys %pheader) {
182      $header{$key} = $pheader{$key} if not exists $header{$key};
183 }
184
185 $fwd .= join("\n",@msg[$i..$#msg]);
186
187 print DEBUG "***\n$fwd\n***\n";
188
189 if (defined $header{'resent-from'} && !defined $header{'from'}) {
190     $header{'from'} = $header{'resent-from'};
191 }
192 defined($header{'from'}) || &quit("no From header");
193
194 my $replyto = $header{'reply-to'};
195 $replyto = '' unless defined $replyto;
196 $replyto =~ s/^ +//;
197 $replyto =~ s/ +$//;
198 unless (length $replyto) {
199     $replyto = $header{'from'};
200 }
201
202 my $subject = '(no subject)';
203 if (!defined($header{'subject'})) 
204 {
205         $brokenness.= fill_template('mail/process_broken_subject');
206
207 } else { 
208     $subject= $header{'subject'}; 
209 }
210
211 my $ref=-1;
212 $subject =~ s/^Re:\s*//i; $_= $subject."\n";
213 if ($tryref < 0 && m/^Bug ?\#(\d+)\D/i) {
214     $tryref= $1+0; 
215 }
216 my $data;
217 if ($tryref >= 0) 
218 {
219      my $bfound;
220     ($bfound, $data)= &lockreadbugmerge($tryref);
221     if ($bfound) { 
222         $ref= $tryref; 
223     } else {
224         &htmllog("Reply","sent", $replyto,"Unknown problem report number <code>$tryref</code>.");
225         &sendmessage(create_mime_message(
226           [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
227            To            => $replyto,
228            Subject       => "Unknown problem report $gBug#$tryref ($subject)",
229            'Message-ID'  => "<handler.x.$nn.unknown\@$gEmailDomain>",
230            'In-Reply-To' => $header{'message-id'},
231            References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
232            Precedence    => 'bulk',
233            "X-$gProject-PR-Message" => 'error',
234           ],message_body_template('process_unknown_bug_number',
235                                   {subject => $subject,
236                                    date    => $header{date},
237                                    baddress => $baddress,
238                                    tryref   => $tryref,
239                                    messageid => $header{'message-id'},
240                                   },
241                                  )),'');
242         &appendlog;
243         &finish;
244     }
245 } else { 
246     &filelock('lock/-1'); 
247 }
248
249 # Attempt to determine which source package this is
250 my $source_pr_header = '';
251 my $source_package = '';
252 if (defined $pheader{source}) {
253      $source_package = $pheader{source};
254 }
255 elsif (defined $data->{package} or defined $pheader{package}) {
256      my $pkg_src = getpkgsrc();
257      $source_package = $pkg_src->{defined $data->{package}?$data->{package}:$pheader{package}};
258 }
259 $source_pr_header = "X-$gProject-PR-Source: $source_package\n"
260      if defined $source_package and length $source_package;
261
262 # Done and Forwarded Bugs
263 if ($codeletter eq 'D' || $codeletter eq 'F') 
264 {
265     if ($replyto =~ m/$gBounceFroms/o ||
266         $header{'from'} =~ m/$gBounceFroms/o)
267     {
268          print STDERR "bounce detected !  Mwaap! Mwaap!";
269          exit 1;
270     }
271     my $markedby= $header{'from'} eq $replyto ? $replyto :
272                "$header{'from'} (reply to $replyto)";
273     my @generalcc;
274     my $receivedat;
275     my $markaswhat;
276     my $set_forwarded;
277     my $generalcc;
278     my $set_done;
279     if ($codeletter eq 'F') { # Forwarded
280         (&appendlog,&finish) if defined $data->{forwarded} and length($data->{forwarded});
281         $receivedat= "forwarded\@$gEmailDomain";
282         $markaswhat= 'forwarded';
283         $set_forwarded= $header{'to'};
284         # Dissallow forwarded being set to this bug tracking system
285         if (defined $set_forwarded and $set_forwarded =~ /\Q$gEmailDomain\E/) {
286              undef $set_forwarded;
287         }
288         if ( length( $gListDomain ) > 0 && length( $gForwardList ) > 0 ) {
289             push @generalcc, "$gForwardList\@$gListDomain";
290             $generalcc= "$gForwardList\@$gListDomain";
291         } else { 
292             $generalcc=''; 
293         }
294     } else { # Done
295         if (defined $data->{done} and length($data->{done}) and
296                 not defined $pheader{'source-version'} and
297                 not defined $pheader{'version'}) {
298             &appendlog;
299             &finish;
300         }
301         $receivedat= "done\@$gEmailDomain";
302         $markaswhat= 'done';
303         $set_done= $header{'from'};
304         if ( length( $gListDomain ) > 0 && length( $gDoneList ) > 0 ) {
305             $generalcc= "$gDoneList\@$gListDomain";
306             push @generalcc, "$gDoneList\@$gListDomain";
307         } else { 
308             $generalcc=''; 
309         }
310     }
311     if (defined $gStrongList and isstrongseverity($data->{severity})) {
312         $generalcc = join ', ', $generalcc, "$gStrongList\@$gListDomain";
313         push @generalcc,"$gStrongList\@$gListDomain";
314     }
315     if ($ref<0) {
316         &htmllog("Warning","sent",$replyto,"Message ignored.");
317         &sendmessage(create_mime_message(
318           [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
319            To            => $replyto,
320            Subject       => "Message with no $gBug number ignored by $receivedat ($subject)",
321            'Message-ID'  => "<handler.x.$nn.warnignore\@$gEmailDomain>",
322            'In-Reply-To' => $header{'message-id'},
323            References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
324            Precedence    => 'bulk',
325            "X-$gProject-PR-Message" => 'error',
326           ],message_body_template('mail/process_no_bug_number',
327                                   {subject => $subject,
328                                    date    => $header{date},
329                                    markaswhat => $markaswhat,
330                                    receivedat => $receivedat,
331                                    messageid => $header{'message-id'},
332                                   },
333                                  )),'');
334         &appendlog;
335         &finish;
336     }
337
338     &checkmaintainers;
339
340     my @noticecc = grep($_ ne $replyto,@maintaddrs);
341     my $noticeccval.= join(', ', grep($_ ne $replyto,@maintaddrs));
342     $noticeccval =~ s/\s+\n\s+/ /g; 
343     $noticeccval =~ s/^\s+/ /; $noticeccval =~ s/\s+$//;
344
345     my @process= ($ref,split(/ /,$data->{mergedwith}));
346     my $orgref= $ref;
347
348     for $ref (@process) {
349         if ($ref != $orgref) {
350             &unfilelock;
351             $data = &lockreadbug($ref)
352                 || die "huh ? $ref from $orgref out of ".join(' ',@process);
353         }
354         $data->{done}= $set_done if defined($set_done);
355         $data->{forwarded}= $set_forwarded if defined($set_forwarded);
356         if ($codeletter eq 'D') {
357             $data->{keywords} = join ' ', grep $_ ne 'pending',
358                                      split ' ', $data->{keywords};
359             if (defined $pheader{'source-version'}) {
360                 addfixedversions($data, $pheader{source}, $pheader{'source-version'}, '');
361             } elsif (defined $pheader{version}) {
362                 addfixedversions($data, $pheader{package}, $pheader{version}, 'binary');
363             }
364         }
365
366         # Add bug mailing list to $generalbcc as appropriate
367         # This array is used to specify bcc in the cases where we're using create_mime_message.
368         my @generalbcc = (@generalcc,@addsrcaddrs,"bugs=$ref\@$gListDomain");
369         my $generalbcc = join(', ', $generalcc, @addsrcaddrs,"bugs=$ref\@$gListDomain");
370         $generalbcc =~ s/\s+\n\s+/ /g;
371         $generalbcc =~ s/^\s+/ /; $generalbcc =~ s/\s+$//;
372         if (length $generalbcc) {$generalbcc = "Bcc: $generalbcc\n"};
373
374         writebug($ref, $data);
375
376         my $hash = get_hashname($ref);
377         open(O,"db-h/$hash/$ref.report") || &quit("read original report: $!");
378         my $orig_report= join('',<O>); close(O);
379         if ($codeletter eq 'F') {
380             &htmllog("Reply","sent",$replyto,"You have marked $gBug as forwarded.");
381             &sendmessage(create_mime_message(
382              ["X-Loop"      => "$gMaintainerEmail",
383               From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
384               To            => "$replyto",
385               Subject       => "$gBug#$ref: marked as forwarded ($data->{subject})",
386               "Message-ID"  => "<header.$ref.$nn.ackfwdd\@$gEmailDomain>",
387               "In-Reply-To" => $header{'message-id'},
388               References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
389               Precedence    => 'bulk',
390               "X-$gProject-PR-Message"  => "forwarded $ref",
391               "X-$gProject-PR-Package"  => $data->{package},
392               "X-$gProject-PR-Keywords" => $data->{keywords},
393               # Only have a X-$gProject-PR-Source when we know the source package
394               (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
395              ],message_body_template('mail/process_mark_as_forwarded',
396                                      {date => $header{date},
397                                       messageid => $header{'message-id'},
398                                       data      => $data,
399                                      },
400                                     ),
401              [join("\n",@msg)]),'',[$replyto,@generalbcc,@noticecc],1);
402         } else {
403             &htmllog("Reply","sent",$replyto,"You have taken responsibility.");
404             &sendmessage(create_mime_message(
405              ["X-Loop"      => "$gMaintainerEmail",
406               From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
407               To            => $replyto,
408               Subject       => "$gBug#$ref: marked as done ($data->{subject})",
409               "Message-ID"  => "<handler.$ref.$nn.ackdone\@$gEmailDomain>",
410               "In-Reply-To" => $header{'message-id'},
411               References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
412               Precedence    => 'bulk',
413               "X-$gProject-PR-Message"  => "closed $ref",
414               "X-$gProject-PR-Package"  => $data->{package},
415               "X-$gProject-PR-Keywords" => $data->{keywords},
416               # Only have a X-$gProject-PR-Source when we know the source package
417               (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
418              ],message_body_template('mail/process_mark_as_done',
419                                      {date => $header{date},
420                                       messageid => $header{'message-id'},
421                                       subject   => $header{subject},
422                                       data      => $data,
423                                      },
424                                     ),
425              [$orig_report,join("\n",@msg)]),'',[$replyto,@generalbcc,@noticecc],1);
426             &htmllog("Notification","sent",$data->{originator},
427                 "$gBug acknowledged by developer.");
428             &sendmessage(create_mime_message(
429              ["X-Loop"      => "$gMaintainerEmail",
430               From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
431               To            => "$data->{originator}",
432               Subject       => "$gBug#$ref closed by $markedby ($header{'subject'})",
433               "Message-ID"  => "<handler.$ref.$nn.notifdone\@$gEmailDomain>",
434               "In-Reply-To" => "$data->{msgid}",
435               References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
436               "X-$gProject-PR-Message"  => "they-closed $ref",
437               "X-$gProject-PR-Package"  => "$data->{package}",
438               "X-$gProject-PR-Keywords" => "$data->{keywords}",
439               # Only have a X-$gProject-PR-Source when we know the source package
440               (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
441               "Reply-To"                => "$ref\@$gEmailDomain",
442               "Content-Type"            => 'text/plain; charset="utf-8"',
443              ],message_body_template('mail/process_your_bug_done',
444                                      {data      => $data,
445                                       markedby  => $markedby,
446                                       messageid => $header{'message-id'},
447                                       subject   => $header{subject},
448                                      },
449                                     ),
450              [join("\n",@msg),$orig_report]),'',undef,1);
451         }
452         &appendlog;
453     }
454     &finish;
455 }
456
457 if ($ref<0) {
458     if ($codeletter eq 'U') {
459         &htmllog("Warning","sent",$replyto,"Message not forwarded.");
460         &sendmessage(create_mime_message(
461           [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
462            To            => $replyto,
463            Subject       => "Message with no $gBug number cannot be sent to submitter! ($subject)",
464            'Message-ID'  => "<handler.x.$nn.nonumnosub\@$gEmailDomain>",
465            'In-Reply-To' => $header{'message-id'},
466            References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
467            Precedence    => 'bulk',
468            "X-$gProject-PR-Message" => 'error',
469           ],message_body_template('mail/process_no_bug_number',
470                                   {subject => $subject,
471                                    date    => $header{date},
472                                    markaswhat => 'submitter',
473                                    receivedat => "$baddress\@$gEmailDomain",
474                                    messageid => $header{'message-id'},
475                                   },
476                                  )),'');
477         &appendlog;
478         &finish;
479     }
480
481     $data->{found_versions} = [];
482     $data->{fixed_versions} = [];
483
484     if (defined $pheader{source}) {
485         $data->{package} = $pheader{source};
486     } elsif (defined $pheader{package}) {
487         $data->{package} = $pheader{package};
488     } elsif (defined $config{default_package}) {
489         $data->{package} = $config{default_package},
490     }
491     else {
492         &htmllog("Warning","sent",$replyto,"Message not forwarded.");
493         my $body = message_body_template('mail/process_no_package',
494                                         );
495         &sendmessage(create_mime_message(
496                        ["X-Loop"      => "$gMaintainerEmail",
497                         From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
498                         To            => $replyto,
499                         Subject       => "Message with no Package: tag cannot be processed! ($subject)",
500                         "Message-ID"  => "<handler.x.$nn.nonumnosub\@$gEmailDomain>",
501                         "In-Reply-To" => $header{'message-id'},
502                         References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
503                         Precedence    => 'bulk',
504                         "X-$gProject-PR-Message" => 'error'
505                        ],
506            message_body_template('mail/process_no_package',
507                                  {date => $header{date},
508                                   subject => $subject,
509                                   messageid => $header{'message-id'},
510                                   baddress => $baddress,
511                                  },
512                                 ),[join("\n", @msg)]), '',undef,1);
513         &appendlog;
514         &finish;
515     }
516
517     if (defined $config{default_package}) {
518          &checkmaintainers;
519          # if there are no maintainers for this package, assign it to the default package
520          if (not @maintaddrs) {
521               $data->{package} = $config{default_package};
522               $brokenness.= fill_template('mail/process_default_package_selected',
523                                           {old_package => $pheader{source} || $pheader{package} || 'No package',
524                                            new_package => $data->{package},
525                                           }
526                                          );
527               # force the maintainers to be rechecked
528               $maintainerschecked = 0;
529               &checkmaintainers;
530          }
531     }
532
533     $data->{keywords}= '';
534     if (defined($pheader{'keywords'})) {
535         $data->{keywords}= $pheader{'keywords'};
536     } elsif (defined($pheader{'tags'})) {
537         $data->{keywords}= $pheader{'tags'};
538     }
539     if (length($data->{keywords})) {
540         my @kws;
541         my %gkws = map { ($_, 1) } @gTags;
542         foreach my $kw (sort split(/[,\s]+/, lc($data->{keywords}))) {
543             push @kws, $kw if (defined $gkws{$kw});
544         }
545         $data->{keywords} = join(" ", @kws);
546     }
547     $data->{severity}= '';
548     if (defined($pheader{'severity'}) || defined($pheader{'priority'})) {
549         $data->{severity}= $pheader{'severity'};
550         $data->{severity}= $pheader{'priority'} unless ($data->{severity});
551         $data->{severity} =~ s/^\s*(.+)\s*$/$1/;
552
553         if (!grep($_ eq $data->{severity}, @gSeverityList, "$gDefaultSeverity")) {
554             $brokenness.= fill_template('mail/invalid_severity',
555                                         {severity=>$data->{severity}}
556                                        );
557             $data->{severity}= '';
558         }
559     }
560     if (defined($pheader{owner})) {
561         $data->{owner}= $pheader{owner};
562     }
563     if (defined($pheader{forwarded})) {
564         $data->{'forwarded-to'} = $pheader{forwarded};
565     }
566     &filelock("nextnumber.lock");
567     open(N,"nextnumber") || &quit("nextnumber: read: $!");
568     my $nextnumber=<N>; $nextnumber =~ s/\n$// || &quit("nextnumber bad format");
569     $ref= $nextnumber+0;  $nextnumber += 1;  $newref=1;
570     &overwrite('nextnumber', "$nextnumber\n");
571     &unfilelock;
572     my $hash = get_hashname($ref);
573     &overwrite("db-h/$hash/$ref.log",'');
574     $data->{originator} = $replyto;
575     $data->{date} = $intdate;
576     $data->{subject} = $subject;
577     $data->{msgid} = $header{'message-id'};
578     writebug($ref, $data);
579     # Deal with usertags
580     if (exists $pheader{usertags}) {
581          my $user = $replyto;
582          $user = $pheader{user} if exists $pheader{user};
583          $user =~ s/,.*//;
584          $user =~ s/^.*<(.*)>.*$/$1/;
585          $user =~ s/[(].*[)]//;
586          $user =~ s/^\s*(\S+)\s+.*$/$1/;
587          if ($user ne '' and Debbugs::User::is_valid_user($user)) {
588               $pheader{usertags} =~ s/(?:^\s+|\s+$)//g;
589               my %user_tags;
590               read_usertags(\%user_tags,$user);
591               for my $tag (split /[,\s]+/, $pheader{usertags}) {
592                    if ($tag =~ /^[a-zA-Z0-9.+\@-]+/) {
593                         my %bugs_with_tag; 
594                         @bugs_with_tag{@{$user_tags{$tag}||[]}} = (1) x @{$user_tags{$tag}||[]};
595                         $bugs_with_tag{$ref} = 1;
596                         $user_tags{$tag} = [keys %bugs_with_tag];
597                    }
598               }
599               write_usertags(\%user_tags,$user);
600          }
601          else {
602               $brokenness .= fill_template('mail/invalid_user',
603                                            {user => $user}
604                                           );
605          }
606     }
607     &overwrite("db-h/$hash/$ref.report",
608                join("\n",@msg)."\n");
609 }
610
611 &checkmaintainers;
612
613 print DEBUG "maintainers >".join(' ',@maintaddrs)."<\n";
614
615 my $orgsender= defined($header{'sender'}) ? "Original-Sender: $header{'sender'}\n" : '';
616 my $newsubject= $subject;  $newsubject =~ s/^$gBug#$ref:*\s*//;
617
618 my $xcchdr= $header{ 'x-debbugs-cc' } || '';
619 if ($xcchdr =~ m/\S/) {
620     push(@resentccs,$xcchdr);
621     $resentccexplain.= fill_template('mail/xdebbugscc',
622                                      {xcchdr => $xcchdr},
623                                     );
624 }
625
626 if (@maintaddrs && ($codeletter eq 'B' || $codeletter eq 'M')) {
627     push(@resentccs,@maintaddrs);
628     $resentccexplain.= fill_template('mail/maintainercc',
629                                      {maintaddrs => \@maintaddrs,
630                                      },
631                                     );
632 }
633
634 @bccs = @addsrcaddrs;
635 if (defined $gStrongList and isstrongseverity($data->{severity})) {
636     push @bccs, "$gStrongList\@$gListDomain";
637 }
638
639 # Send mail to the per bug list subscription too
640 push @bccs, "bugs=$ref\@$gListDomain";
641
642 if (defined $pheader{source}) {
643     # Prefix source versions with the name of the source package. They
644     # appear that way in version trees so that we can deal with binary
645     # packages moving from one source package to another.
646     if (defined $pheader{'source-version'}) {
647         addfoundversions($data, $pheader{source}, $pheader{'source-version'}, '');
648     } elsif (defined $pheader{version}) {
649         addfoundversions($data, $pheader{source}, $pheader{version}, '');
650     }
651     writebug($ref, $data);
652 } elsif (defined $pheader{package}) {
653     # TODO: could handle Source-Version: by looking up the source package?
654     addfoundversions($data, $pheader{package}, $pheader{version}, 'binary');
655     writebug($ref, $data);
656 }
657
658 my $veryquiet= $codeletter eq 'Q';
659 if ($codeletter eq 'M' && !@maintaddrs) {
660     $veryquiet= 1;
661     $brokenness.= fill_template('mail/invalid_maintainer',
662                                 {},
663                                );
664 }
665
666 my $resentccval.= join(', ',@resentccs);
667 $resentccval =~ s/\s+\n\s+/ /g; $resentccval =~ s/^\s+/ /; $resentccval =~ s/\s+$//;
668 my $resentcc = '';
669 if (length($resentccval)) { 
670     $resentcc= "Resent-CC: $resentccval\n"; 
671 }
672
673 if ($codeletter eq 'U') {
674     &htmllog("Message", "sent on", $data->{originator}, "$gBug#$ref.");
675     &sendmessage(<<END,[$data->{originator},@resentccs],[@bccs]);
676 Subject: $gBug#$ref: $newsubject
677 Reply-To: $replyto, $ref-quiet\@$gEmailDomain
678 ${orgsender}Resent-To: $data->{originator}
679 ${resentcc}Resent-Date: $tdate
680 Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
681 Resent-Sender: $gMaintainerEmail
682 X-$gProject-PR-Message: report $ref
683 X-$gProject-PR-Package: $data->{package}
684 X-$gProject-PR-Keywords: $data->{keywords}
685 ${source_pr_header}$fwd
686 END
687 } elsif ($codeletter eq 'B') { # Sent to submit
688     &htmllog($newref ? "Report" : "Information", "forwarded",
689              join(', ',"$gSubmitList\@$gListDomain",@resentccs),
690              "<code>$gBug#$ref</code>".
691              (length($data->{package})? "; Package <code>".html_escape($data->{package})."</code>" : '').
692              ".");
693     &sendmessage(<<END,["$gSubmitList\@$gListDomain",@resentccs],[@bccs]);
694 Subject: $gBug#$ref: $newsubject
695 Reply-To: $replyto, $ref\@$gEmailDomain
696 Resent-From: $header{'from'}
697 ${orgsender}Resent-To: $gSubmitList\@$gListDomain
698 ${resentcc}Resent-Date: $tdate
699 Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
700 Resent-Sender: $gMaintainerEmail
701 X-$gProject-PR-Message: report $ref
702 X-$gProject-PR-Package: $data->{package}
703 X-$gProject-PR-Keywords: $data->{keywords}
704 ${source_pr_header}$fwd
705 END
706 } elsif (@resentccs or @bccs) { # Quiet or Maintainer
707     # D and F done far earlier; B just done - so this must be M or Q
708     # We preserve whichever it was in the Reply-To (possibly adding
709     # the $gBug#).
710     if (@resentccs) {
711         &htmllog($newref ? "Report" : "Information", "forwarded",
712                  $resentccval,
713                  "<code>$gBug#$ref</code>".
714                  (length($data->{package}) ? "; Package <code>".html_escape($data->{package})."</code>" : '').
715                  ".");
716     } else {
717         &htmllog($newref ? "Report" : "Information", "stored",
718                  "",
719                  "<code>$gBug#$ref</code>".
720                  (length($data->{package}) ? "; Package <code>".html_escape($data->{package})."</code>" : '').
721                  ".");
722     }
723     &sendmessage(<<END,[@resentccs],[@bccs]);
724 Subject: $gBug#$ref: $newsubject
725 Reply-To: $replyto, $ref-$baddressroot\@$gEmailDomain
726 Resent-From: $header{'from'}
727 ${orgsender}Resent-To: $resentccval
728 Resent-Date: $tdate
729 Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
730 Resent-Sender: $gMaintainerEmail
731 X-$gProject-PR-Message: report $ref
732 X-$gProject-PR-Package: $data->{package}
733 X-$gProject-PR-Keywords: $data->{keywords}
734 ${source_pr_header}$fwd
735 END
736 }
737
738 my $htmlbreak= length($brokenness) ? "<p>\n".html_escape($brokenness)."\n<p>\n" : '';
739 $htmlbreak =~ s/\n\n/\n<P>\n\n/g;
740 if (length($resentccval)) {
741     $htmlbreak = "  Copy sent to <code>".html_escape($resentccval)."</code>.".
742         $htmlbreak;
743 }
744
745 # Should we send an ack out?
746 if (not exists $header{'x-debbugs-no-ack'} and
747     ($newref or
748      ($codeletter ne 'U' and
749       (not defined $header{precedence} or
750        $header{'precedence'} !~ /\b(?:bulk|junk|list)\b/
751       )
752      )
753     )
754    ){
755
756      # figure out forward explanation
757      my $forwardexplain = '';
758      my $thanks = '';
759      my $extra_vars;
760      # will contain info and -info in moreinfo messages
761      my $info = '';
762      my $infod = '';
763      # temporary headers
764      my %t_h;
765      if ($newref) {
766           &htmllog("Acknowledgement","sent",$replyto,
767                    ($veryquiet ?
768                     "New $gBug report received and filed, but not forwarded." :
769                     "New $gBug report received and forwarded."). $htmlbreak);
770           $thanks = fill_template('mail/process_ack_thanks_new');
771      }
772      else {
773           &htmllog("Acknowledgement","sent",$replyto,
774                    ($veryquiet ? "Extra info received and filed, but not forwarded." :
775                     $codeletter eq 'M' ? "Extra info received and forwarded to maintainer." :
776                     "Extra info received and forwarded to list."). $htmlbreak);
777           $thanks = fill_template('mail/process_ack_thanks_additional');
778           $info = 'info';
779           $infod = '-info';
780      }
781      if ($veryquiet) {
782           $forwardexplain = fill_template('mail/forward_veryquiet',
783                                          );
784           # these are the headers that quiet messages override
785           $t_h{messageid}  = "<handler.$ref.$nn.ack${info}quiet\@$gEmailDomain>";
786           $t_h{pr_message} = "ack${infod}-quiet $ref";
787           $t_h{reply_to}   = "$ref-quiet\@$gEmailDomain";
788           $extra_vars->{refreplyto} = "$ref-quiet\@$gEmailDomain";
789           $t_h{subject}    = length($info)?
790                "$gBug#$ref: Info received and FILED only ($subject)":
791                "$gBug#$ref: Acknowledgement of QUIET report ($subject)";
792      }
793      elsif ($codeletter eq 'M') {
794           $forwardexplain = fill_template('mail/forward_maintonly',
795                                          );
796           # these are the headers that maintonly messages override
797           $t_h{messageid}  = "<handler.$ref.$nn.ack{$info}maintonly\@$gEmailDomain>";
798           $t_h{pr_message} = "ack${infod}-maintonly $ref";
799           $t_h{reply_to}   = "$ref-maintonly\@$gEmailDomain";
800           $extra_vars->{refreplyto} = "$ref-maintonly\@$gEmailDomain";
801           $t_h{subject}    = length($info)?
802                "$gBug#$ref: Info received for maintainer only ($subject)":
803                "$gBug#$ref: Acknowledgement of maintainer-only report ($subject)";
804      }
805      else {
806           $forwardexplain = fill_template('mail/forward_normal',
807                                          );
808           $t_h{messageid}  = "<handler.$ref.$nn.ack${info}\@$gEmailDomain>";
809           $t_h{pr_message} = "ack${infod} $ref";
810           $t_h{reply_to}   = "$ref\@$gEmailDomain";
811           $extra_vars->{refreplyto} = "$ref\@$gEmailDomain";
812           $t_h{subject}    = (defined $info and length($info))?
813                "$gBug#$ref: Info received ($subject)" :
814                "$gBug#$ref: Acknowledgement ($subject)";
815      }
816      my $body = message_body_template('mail/process_ack',
817                                       {forwardexplain  => $forwardexplain,
818                                        resentccexplain => $resentccexplain,
819                                        thanks          => $thanks,
820                                        %{$extra_vars}
821                                       }
822                                      );
823      &sendmessage(create_mime_message(
824                        ["X-Loop"      => "$gMaintainerEmail",
825                         From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
826                         To            => $replyto,
827                         Subject       => $t_h{subject},
828                         "Message-ID"  => $t_h{messageid},
829                         "In-Reply-To" => $header{'message-id'},
830                         References    => $header{'message-id'},
831                         Precedence    => 'bulk',
832                         "X-$gProject-PR-Message"  => $t_h{pr_message} || "ack $ref",
833                         "X-$gProject-PR-Package"  => $data->{package},
834                         "X-$gProject-PR-Keywords" => $data->{keywords},
835                         # Only have a X-$gProject-PR-Source when we know the source package
836                         (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
837                         "Reply-To"                => $t_h{reply_to} || "$ref\@$gEmailDomain",
838                        ],$body,[]), '',undef,1);
839 }
840
841 &appendlog;
842 &finish;
843
844 sub overwrite {
845     my ($f,$v) = @_;
846     open(NEW,">$f.new") || &quit("$f.new: create: $!");
847     print(NEW "$v") || &quit("$f.new: write: $!");
848     close(NEW) || &quit("$f.new: close: $!");
849     rename("$f.new","$f") || &quit("rename $f.new to $f: $!");
850 }
851
852 sub appendlog {
853     my $hash = get_hashname($ref);
854     if (!open(AP,">>db-h/$hash/$ref.log")) {
855         print DEBUG "failed open log<\n";
856         print DEBUG "failed open log err $!<\n";
857         &quit("opening db-h/$hash/$ref.log (li): $!");
858     }
859     print(AP "\7\n",escape_log(@log),"\n\3\n") || &quit("writing db-h/$hash/$ref.log (li): $!");
860     close(AP) || &quit("closing db-h/$hash/$ref.log (li): $!");
861 }
862
863 sub finish {
864     my ($exit) = @_;
865     $exit ||= 0;
866     utime(time,time,"db");
867     # cleanups are run in an end block now.
868     #my ($u);
869     #while ($u= $cleanups[$#cleanups]) { &$u; }
870     unlink("incoming/P$nn") || &quit("unlinking incoming/P$nn: $!");
871     exit $exit;
872 }
873
874 &quit("wot no exit");
875
876 sub htmllog {
877     my ($whatobj,$whatverb,$where,$desc) = @_;
878     my $hash = get_hashname($ref);
879     open(AP,">>db-h/$hash/$ref.log") || &quit("opening db-h/$hash/$ref.log (lh): $!");
880     print(AP
881           "\6\n".
882           "<strong>$whatobj $whatverb</strong>".
883           ($where eq '' ? "" : " to <code>".html_escape($where)."</code>").
884           ":<br>\n". $desc.
885           "\n\3\n") || &quit("writing db-h/$hash/$ref.log (lh): $!");
886     close(AP) || &quit("closing db-h/$hash/$ref.log (lh): $!");
887 }    
888
889 sub stripbccs {
890     my $msg = shift;
891     my $ret = '';
892     my $bcc = 0;
893     while ($msg =~ s/(.*\n)//) {
894         local $_ = $1;
895         if (/^$/) {
896             $ret .= $_;
897             last;
898         }
899         if ($bcc) {
900             # strip continuation lines too
901             next if /^\s/;
902             $bcc = 0;
903         }
904         if (/^Bcc:/i) {
905             $bcc = 1;
906         } else {
907             $ret .= $_;
908         }
909     }
910     return $ret . $msg;
911 }
912
913 =head2 send_message
914
915      send_message($the_message,\@recipients,\@bcc,$do_not_encode)
916
917 The first argument is the scalar message, the second argument is the
918 arrayref of recipients, the third is the arrayref of Bcc:'ed
919 recipients.
920
921 The final argument turns off header encoding and the addition of the
922 X-Loop header if true, defaults to false.
923
924 =cut
925
926
927 sub sendmessage {
928     my ($msg,$recips,$bcc,$no_encode) = @_;
929     if (not defined $recips or (!ref($recips) && $recips eq '')
930         or @$recips == 0) {
931         $recips = ['-t'];
932     }
933     # This is suboptimal. The right solution is to send headers
934     # separately from the rest of the message and encode them rather
935     # than doing this.
936     $msg = "X-Loop: $gMaintainerEmail\n" . $msg unless $no_encode;
937     # The original message received is written out in appendlog, so
938     # before writing out the other messages we've sent out, we need to
939     # RFC1522 encode the header.
940     $msg = encode_headers($msg) unless $no_encode;
941
942     my $hash = get_hashname($ref);
943     #save email to the log
944     open(AP,">>db-h/$hash/$ref.log") || &quit("opening db-h/$hash/$ref.log (lo): $!");
945     print(AP "\2\n",join("\4",@$recips),"\n\5\n",
946           escape_log(stripbccs($msg)),"\n\3\n") ||
947         &quit("writing db-h/$hash/$ref.log (lo): $!");
948     close(AP) || &quit("closing db-h/$hash/$ref.log (lo): $!");
949
950     if (ref($bcc)) {
951         shift @$recips if $recips->[0] eq '-t';
952         push @$recips, @$bcc;
953     }
954
955     send_mail_message(message        => $msg,
956                       # Because we encode the headers above, we do not want to encode them here
957                       encode_headers => 0,
958                       recipients     => $recips);
959 }
960
961 =head2 message_body_template
962
963      message_body_template('mail/ack',{ref=>'foo'});
964
965 Creates a message body using a template
966
967 =cut
968
969 sub message_body_template{
970      my ($template,$extra_var) = @_;
971      $extra_var ||={};
972      my $body = fill_template($template,$extra_var);
973      return fill_template('mail/message_body',
974                           {%{$extra_var},
975                            body => $body,
976                           },
977                          );
978 }
979
980 =head2 fill_template
981
982      fill_template('mail/foo',{foo=>'bar'});
983
984 Calls fill_in_template with a default set of variables and any extras
985 added in.
986
987 =cut
988
989 sub fill_template{
990      my ($template,$extra_var) = @_;
991      $extra_var ||={};
992      my $variables = {config => \%config,
993                       defined($ref)?(ref    => $ref):(),
994                       defined($data)?(data  => $data):(),
995                       %{$extra_var},
996                      };
997      my $hole_var = {'&bugurl' =>
998                      sub{"$_[0]: ".
999                               'http://'.$config{cgi_domain}.'/'.
1000                                    Debbugs::CGI::bug_url($_[0]);
1001                     }
1002                     };
1003      return fill_in_template(template => $template,
1004                              variables => $variables,
1005                              hole_var  => $hole_var,
1006                             );
1007 }
1008
1009
1010 sub checkmaintainers {
1011     return if $maintainerschecked++;
1012     return if !length($data->{package});
1013     my %maintainerof;
1014     open(MAINT,"$gMaintainerFile") || die &quit("maintainers open: $!");
1015     while (<MAINT>) {
1016         m/^\n$/ && next;
1017         m/^\s*$/ && next;
1018         m/^(\S+)\s+(\S.*\S)\s*\n$/ || &quit("maintainers bogus \`$_'");
1019         $a= $1; $b= $2; $a =~ y/A-Z/a-z/;
1020         # use the package which is normalized to lower case; we do this because we lc the pseudo headers.
1021         $maintainerof{$a}= $2;
1022     }
1023     close(MAINT);
1024     open(MAINT,"$gMaintainerFileOverride") || die &quit("maintainers.override open: $!");
1025     while (<MAINT>) {
1026         m/^\n$/ && next;
1027         m/^\s*$/ && next;
1028         m/^(\S+)\s+(\S.*\S)\s*\n$/ || &quit("maintainers.override bogus \`$_'");
1029         $a= $1; $b= $2; $a =~ y/A-Z/a-z/;
1030         # use the package which is normalized to lower case; we do this because we lc the pseudo headers.
1031         $maintainerof{$a}= $2;
1032     }
1033     close(MAINT);
1034     my %pkgsrc;
1035     open(SOURCES,"$gPackageSource") || &quit("pkgsrc open: $!");
1036     while (<SOURCES>) {
1037         next unless m/^(\S+)\s+\S+\s+(\S.*\S)\s*$/;
1038         ($a,$b)=($1,$2);
1039         $a =~ y/A-Z/a-z/;
1040         $pkgsrc{$a} = $b;
1041     }
1042     close(SOURCES);
1043     my $anymaintfound=0; my $anymaintnotfound=0;
1044     for my $p (split(m/[ \t?,():]+/,$data->{package})) {
1045         $p =~ y/A-Z/a-z/;
1046         $p =~ /([a-z0-9.+-]+)/;
1047         $p = $1;
1048         next unless defined $p;
1049         if (defined $gSubscriptionDomain) {
1050             if (defined($pkgsrc{$p})) {
1051                 push @addsrcaddrs, "$pkgsrc{$p}\@$gSubscriptionDomain";
1052             } else {
1053                 push @addsrcaddrs, "$p\@$gSubscriptionDomain";
1054             }
1055         }
1056         if (defined($maintainerof{$p})) {
1057             print DEBUG "maintainer add >$p|$maintainerof{$p}<\n";
1058             my $addmaint= $maintainerof{$p};
1059             push(@maintaddrs,$addmaint) unless
1060                 $addmaint eq $replyto || grep($_ eq $addmaint, @maintaddrs);
1061             $anymaintfound++;
1062         } else {
1063             print DEBUG "maintainer none >$p<\n";
1064             push(@maintaddrs,$gUnknownMaintainerEmail) unless $anymaintnotfound;
1065             $anymaintnotfound++;
1066             last;
1067         }
1068     }
1069
1070     if (defined $data->{owner} and length $data->{owner}) {
1071         print DEBUG "owner add >$data->{package}|$data->{owner}<\n";
1072         my $addmaint = $data->{owner};
1073         push(@maintaddrs, $addmaint) unless
1074             $addmaint eq $replyto or grep($_ eq $addmaint, @maintaddrs);
1075     }
1076 }
1077
1078 =head2 bug_list_forward
1079
1080      bug_list_forward($spool_filename) if $codeletter eq 'L';
1081
1082
1083 Given the spool file, will forward a bug to the per bug mailing list
1084 subscription system.
1085
1086 =cut
1087
1088 sub bug_list_forward{
1089      my ($bug_fn) = @_;
1090      # Read the bug information and package information for passing to
1091      # the mailing list
1092      my ($bug_number) = $bug_fn =~ /^L(\d+)\./;
1093      my ($bfound, $data)= lockreadbugmerge($bug_number);
1094      my $bug_fh = new IO::File "incoming/P$bug_fn" or die "Unable to open incoming/P$bug_fn $!";
1095
1096      local $/ = undef;
1097      my $bug_message = <$bug_fh>;
1098      my ($bug_address) = $bug_message =~ /^Received: \(at ([^\)]+)\) by/;
1099      my ($envelope_from) = $bug_message =~ s/\nFrom\s+([^\s]+)[^\n]+\n/\n/;
1100      if (not defined $envelope_from) {
1101           # Try to use the From: header or something to set it 
1102           ($envelope_from) = $bug_message =~ /\nFrom:\s+(.+?)\n/;
1103           # Kludgy, and should really be using a full scale header
1104           # parser to do this.
1105           $envelope_from =~ s/^.+?<([^>]+)>.+$/$1/;
1106      }
1107      my ($header,$body) = split /\n\n/, $bug_message, 2;
1108      # Add X-$gProject-PR-Message: list bug_number, package name, and bug title headers
1109      $header .= qq(\nX-$gProject-PR-Message: list $bug_number\n).
1110           qq(X-$gProject-PR-Package: $data->{package}\n).
1111                qq(X-$gProject-PR-Title: $data->{subject})
1112                if defined $data;
1113      print STDERR "Tried to loop me with $envelope_from\n"
1114           and exit 1 if $envelope_from =~ /\Q$gListDomain\E|\Q$gEmailDomain\E/;
1115      print DEBUG $envelope_from,qq(\n);
1116      # If we don't have a bug address, something has gone horribly wrong.
1117      print STDERR "Doesn't match: $bug_address\n" and exit 1 unless defined $bug_address;
1118      $bug_address =~ s/\@.+//;
1119      print DEBUG "Sending message to bugs=$bug_address\@$gListDomain\n";
1120      print DEBUG $header.qq(\n\n).$body;
1121      send_mail_message(message        => $header.qq(\n\n).$body,
1122                        recipients     => ["bugs=$bug_address\@$gListDomain"],
1123                        envelope_from  => $envelope_from,
1124                        encode_headers => 0,
1125                       );
1126      unlink("incoming/P$bug_fn") || &quit("unlinking incoming/P$bug_fn: $!");
1127      exit 0;
1128 }