X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fservice.in;h=1cf84601d521e8248c3db4149149cd9b6f0841ca;hb=a73f9e31eb7f07cd3790b219e2b29070bce19104;hp=d1c6e9015146550e8a39e4b5f98d742b8e15a061;hpb=e1071ed8ae021ce9da2cf01014debd3b6fc53ac0;p=debbugs.git diff --git a/scripts/service.in b/scripts/service.in index d1c6e90..1cf8460 100755 --- a/scripts/service.in +++ b/scripts/service.in @@ -9,11 +9,18 @@ use MIME::Parser; use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522); use Debbugs::Mail qw(send_mail_message); use Debbugs::User; +use HTML::Entities qw(encode_entities); +use Debbugs::Versions::Dpkg; -$config_path = '/etc/debbugs'; -$lib_path = '/usr/lib/debbugs'; +use Debbugs::Config qw(:globals :config); +use Debbugs::CGI qw(html_escape); +use Debbugs::Control qw(:archive :log); +use Debbugs::Log qw(:misc); +use Debbugs::Text qw(:templates); -require "$config_path/config"; +use Mail::RFC822::Address; + +$lib_path = $gLibPath; require "$lib_path/errorlib"; $ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'}; @@ -54,12 +61,13 @@ my (@headerlines, @bodylines); my (%bug_affected); if ($entity and $entity->head->tags) { - @headerlines = @{$entity->head->header}; - chomp @headerlines; + # Use map instead of chomp to also kill \r. + @headerlines = map {s/\r?\n?$//; $_;} + @{$entity->head->header}; my $entity_body = getmailbody($entity); - @bodylines = $entity_body ? $entity_body->as_lines() : (); - chomp @bodylines; + @bodylines = map {s/\r\n$//; $_;} + $entity_body ? $entity_body->as_lines() : (); } else { # Legacy pre-MIME code, kept around in case MIME::Parser fails. my $i; @@ -121,6 +129,8 @@ if ( defined($header{'reply-to'}) && $header{'reply-to'} ne "" ) { $replyto = $header{'from'}; } +# This is an error counter which should be incremented every time there is an error. +my $errors = 0; $controlrequestaddr= $control ? "control\@$gEmailDomain" : "request\@$gEmailDomain"; $transcript=''; &transcript("Processing commands for $controlrequestaddr:\n\n"); @@ -138,12 +148,13 @@ $user =~ s/^.*<(.*)>.*$/$1/; $user =~ s/[(].*[)]//; $user =~ s/^\s*(\S+)\s+.*$/$1/; $user = "" unless (Debbugs::User::is_valid_user($user)); +my $indicated_user = 0; my $quickabort = 0; -my $fuckheads = "(" . join("|", @gFuckheads) . ")"; -if (@gFuckheads and $replyto =~ m/$fuckheads/) { - &transcript("This service is unavailable.\n\n"); +my $fuckheads = "(" . join("|", @gExcludeFromControl) . ")"; +if (@gExcludeFromControl and $replyto =~ m/$fuckheads/) { + &transcript(fill_template('mail/excluded_from_control')); $quickabort = 1; } @@ -168,7 +179,7 @@ for ($procline=0; $procline<=$#bodylines; $procline++) { &transcript("> $_\n"); next if m/^\s*\#/; $action= ''; - if (m/^stop/i || m/^quit/i || m/^--/ || m/^thank/i || m/^kthxbye/i) { + if (m/^stop\s*$/i || m/^quit\s*$/i || m/^--\s*$/ || m/^thank(?:s|\s*you)?\s*$/i || m/^kthxbye\s*$/i) { &transcript("Stopping processing here.\n\n"); last; } elsif (m/^debug\s+(\d+)$/i && $1 >= 0 && $1 <= 1000) { @@ -183,12 +194,15 @@ for ($procline=0; $procline<=$#bodylines; $procline++) { "detailed logs for $gBug#$ref"); } elsif (m/^index(\s+full)?$/i) { &transcript("This BTS function is currently disabled, sorry.\n\n"); + $errors++; $ok++; # well, it's not really ok, but it fixes #81224 :) } elsif (m/^index-summary\s+by-package$/i) { &transcript("This BTS function is currently disabled, sorry.\n\n"); + $errors++; $ok++; # well, it's not really ok, but it fixes #81224 :) } elsif (m/^index-summary(\s+by-number)?$/i) { &transcript("This BTS function is currently disabled, sorry.\n\n"); + $errors++; $ok++; # well, it's not really ok, but it fixes #81224 :) } elsif (m/^index(\s+|-)pack(age)?s?$/i) { &sendlynxdoc("pkgindex.cgi?indexon=pkg",'index of packages'); @@ -206,12 +220,15 @@ for ($procline=0; $procline<=$#bodylines; $procline++) { $ok++; } elsif (m/^send-unmatched(\s+this|\s+-?0)?$/i) { &transcript("This BTS function is currently disabled, sorry.\n\n"); + $errors++; $ok++; # well, it's not really ok, but it fixes #81224 :) } elsif (m/^send-unmatched\s+(last|-1)$/i) { &transcript("This BTS function is currently disabled, sorry.\n\n"); + $errors++; $ok++; # well, it's not really ok, but it fixes #81224 :) } elsif (m/^send-unmatched\s+(old|-2)$/i) { &transcript("This BTS function is currently disabled, sorry.\n\n"); + $errors++; $ok++; # well, it's not really ok, but it fixes #81224 :) } elsif (m/^getinfo\s+([\w-.]+)$/i) { # the following is basically a Debian-specific kludge, but who cares @@ -250,9 +267,12 @@ END my $olduser = ($user ne "" ? " (was $user)" : ""); &transcript("Setting user to $newuser$olduser.\n"); $user = $newuser; + $indicated_user = 1; } else { &transcript("Selected user id ($newuser) invalid, sorry\n"); + $errors++; $user = ""; + $indicated_user = 1; } } elsif (m/^usercategory\s+(\S+)(\s+\[hidden\])?\s*$/i) { $ok++; @@ -263,7 +283,16 @@ END my @cats; my $bad = 0; my $catsec = 0; - while (++$procline <= $#bodylines) { + if ($user eq "") { + &transcript("No valid user selected\n"); + $errors++; + next; + } + if (not $indicated_user and defined $user) { + &transcript("User is $user\n"); + $indicated_user = 1; + } + while (++$procline <= $#bodylines) { unless ($bodylines[$procline] =~ m/^\s*([*+])\s*(\S.*)$/) { $procline--; last; @@ -273,6 +302,7 @@ END my ($o, $txt) = ($1, $2); if ($#cats == -1 && $o eq "+") { &transcript("User defined category specification must start with a category name. Skipping.\n\n"); + $errors++; $bad = 1; next; } @@ -291,6 +321,7 @@ END $desc = ""; $op = $1; } else { &transcript("Unrecognised syntax for category section. Skipping.\n\n"); + $errors++; $bad = 1; next; } @@ -324,17 +355,30 @@ END if (@cats) { &transcript("Added usercategory $catname.\n\n"); $u->{"categories"}->{$catname} = [ @cats ]; + if (not $hidden) { + push @{$u->{visible_cats}},$catname; + } } else { &transcript("Removed usercategory $catname.\n\n"); delete $u->{"categories"}->{$catname}; + @{$u->{visible_cats}} = grep {$_ ne $catname} @{$u->{visible_cats}}; } $u->write(); } elsif (m/^usertags?\s+\#?(-?\d+)\s+(([=+-])\s*)?(\S.*)?$/i) { $ok++; $ref = $1; $addsubcode = $3 || "+"; $tags = $4; + if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) { + $ref = $clonebugs{$ref}; + } if ($user eq "") { &transcript("No valid user selected\n"); + $errors++; + $indicated_user = 1; } elsif (&setbug) { + if (not $indicated_user and defined $user) { + &transcript("User is $user\n"); + $indicated_user = 1; + } &nochangebug; my %ut; Debbugs::User::read_usertags(\%ut, $user); @@ -349,6 +393,7 @@ END } if (@badtags) { &transcript("Ignoring illegal tag/s: ".join(', ', @badtags).".\nPlease use only alphanumerics, at, dot, plus and dash.\n"); + $errors++; } for my $t (keys %chtags) { $ut{$t} = [] unless defined $ut{$t}; @@ -378,6 +423,7 @@ Unknown command or malformed arguments to command. (Use control\@$gEmailDomain to manipulate reports.) END + $errors++; if (++$unknowns >= 3) { &transcript("Too many unknown commands, stopping here.\n\n"); last; @@ -473,7 +519,7 @@ END $bug_affected{$ref}=1; if (&setbug) { if (@{$data->{fixed_versions}}) { - &transcript("'reopen' is deprecated when a bug has been closed with a version;\nuse 'found' or 'submitter' as appropriate instead.\n"); + &transcript("'reopen' may be inappropriate when a bug has been closed with a version;\nyou may need to use 'found' to remove fixed versions.\n"); } if (!length($data->{done})) { &transcript("$gBug is already open, cannot reopen.\n\n"); @@ -490,13 +536,16 @@ END } while (&getnextbug); } } - } elsif (m/^found\s+\#?(-?\d+)(?:\s+(\d.*))?$/i) { + } elsif (m{^found\s+\#?(-?\d+) + (?:\s+((?:$config{package_name_re}\/)? + $config{package_version_re}))?$}ix) { $ok++; $ref= $1; $version= $2; if (&setbug) { if (!length($data->{done}) and not defined($version)) { &transcript("$gBug is already open, cannot reopen.\n\n"); + $errors++; &nochangebug; } else { $action= @@ -509,17 +558,19 @@ END # tracking, because a bug may be closed by multiple # people in different branches. Until we have something # more flexible, we set it every time a bug is fixed, - # and clear it precisely when a found command is - # received for the rightmost fixed-in version, which - # equates to the most recent fixing of the bug, or when - # a versionless found command is received. - if (defined $version) { - my $lastfixed = $data->{fixed_versions}[-1]; - # TODO: what if $data->{package} is a source package? + # and clear it when a bug is found in a version greater + # than any version in which the bug is fixed or when + # a bug is found and there is no fixed version + if (defined $version) { + my ($version_only) = $version =~ m{([^/]+)$}; addfoundversions($data, $data->{package}, $version, 'binary'); - if (defined $lastfixed and not grep { $_ eq $lastfixed } @{$data->{fixed_versions}}) { - $data->{done} = ''; - } + my @fixed_order = sort {Debbugs::Versions::Dpkg::vercmp($a,$b);} + map {s{.+/}{}; $_;} @{$data->{fixed_versions}}; + if (not @fixed_order or (Debbugs::Versions::Dpkg::vercmp($version_only,$fixed_order[-1]) >= 0)) { + $action = "$gBug marked as found in version $version and reopened." + if length $data->{done}; + $data->{done} = ''; + } } else { # Versionless found; assume old-style "not fixed at # all". @@ -529,12 +580,14 @@ END } while (&getnextbug); } } - } elsif (m/^notfound\s+\#?(-?\d+)\s+(\d.*)$/i) { + } elsif (m[^notfound\s+\#?(-?\d+)\s+ + ((?:$config{package_name_re}\/)? + \S+)\s*$]ix) { $ok++; $ref= $1; $version= $2; if (&setbug) { - $action= "$gBug marked as not found in version $version."; + $action= "$gBug no longer marked as found in version $version."; if (length($data->{done})) { $extramessage= "(By the way, this $gBug is currently marked as done.)\n"; } @@ -542,8 +595,43 @@ END &addmaintainers($data); removefoundversions($data, $data->{package}, $version, 'binary'); } while (&getnextbug); - } - } elsif (m/^submitter\s+\#?(-?\d+)\s+\!$/i ? ($newsubmitter=$replyto, 1) : + } + } + elsif (m[^fixed\s+\#?(-?\d+)\s+ + ((?:$config{package_name_re}\/)? + $config{package_version_re})\s*$]ix) { + $ok++; + $ref= $1; + $version= $2; + if (&setbug) { + $action= + defined($version) ? + "$gBug marked as fixed in version $version." : + "$gBug reopened."; + do { + &addmaintainers($data); + addfixedversions($data, $data->{package}, $version, 'binary'); + } while (&getnextbug); + } + } + elsif (m[^notfixed\s+\#?(-?\d+)\s+ + ((?:$config{package_name_re}\/)? + \S+)\s*$]ix) { + $ok++; + $ref= $1; + $version= $2; + if (&setbug) { + $action= + defined($version) ? + "$gBug no longer marked as fixed in version $version." : + "$gBug reopened."; + do { + &addmaintainers($data); + removefixedversions($data, $data->{package}, $version, 'binary'); + } while (&getnextbug); + } + } + elsif (m/^submitter\s+\#?(-?\d+)\s+\!$/i ? ($newsubmitter=$replyto, 1) : m/^submitter\s+\#?(-?\d+)\s+(\S.*\S)$/i ? ($newsubmitter=$2, 1) : 0) { $ok++; $ref= $1; @@ -551,7 +639,11 @@ END if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) { $ref = $clonebugs{$ref}; } - if (&getbug) { + if (not Mail::RFC822::Address::valid($newsubmitter)) { + transcript("$newsubmitter is not a valid e-mail address; not changing submitter\n"); + $errors++; + } + elsif (&getbug) { if (&checkpkglimit) { &foundbug; &addmaintainers($data); @@ -645,9 +737,11 @@ END if (!grep($_ eq $newseverity, @gSeverityList, "$gDefaultSeverity")) { &transcript("Severity level \`$newseverity' is not known.\n". "Recognized are: $gShowSeverities.\n\n"); + $errors++; } elsif (exists $gObsoleteSeverities{$newseverity}) { &transcript("Severity level \`$newseverity' is obsolete. " . - "$gObsoleteSeverities{$newseverity}\n\n"); + "Use $gObsoleteSeverities{$newseverity} instead.\n\n"); + $errors++; } elsif (&setbug) { $printseverity= $data->{severity}; $printseverity= "$gDefaultSeverity" if $printseverity eq ''; @@ -682,6 +776,7 @@ END if (@badtags) { &transcript("Unknown tag/s: ".join(', ', @badtags).".\n". "Recognized are: ".join(' ', @gTags).".\n\n"); + $errors++; } if (&setbug) { if ($data->{keywords} eq '') { @@ -715,7 +810,7 @@ END $data->{keywords} =~ s/\s*$//; } while (&getnextbug); } - } elsif (m/^(un)?block\s+\#?(-?\d+)\s+(by|with)\s+\s*(\S.*)?$/i) { + } elsif (m/^(un)?block\s+\#?(-?\d+)\s+(by|with)\s+(\S.*)?$/i) { $ok++; my $bugnum = $2; my $blockers = $4; $addsub = "add"; @@ -734,7 +829,8 @@ END $ref = $clonebugs{$ref}; } if (&getbug) { - push @okayblockers, $b; + &foundbug; + push @okayblockers, $ref; # add to the list all bugs that are merged with $b, # because all of their data must be kept in sync @@ -750,7 +846,7 @@ END } else { ¬foundbug; - push @badblockers, $b; + push @badblockers, $ref; } } else { @@ -759,6 +855,7 @@ END } if (@badblockers) { &transcript("Unknown blocking bug/s: ".join(', ', @badblockers).".\n"); + $errors++; } $ref=$bugnum; @@ -828,8 +925,9 @@ END if (&checkpkglimit) { &foundbug; &addmaintainers($data); + my $oldtitle = $data->{subject}; $data->{subject}= $newtitle; - $action= "Changed $gBug title."; + $action= "Changed $gBug title to `$newtitle' from `$oldtitle'."; &savebug; &transcript("$action\n"); if (length($data->{done})) { @@ -897,6 +995,7 @@ END if (length($mismatch)) { &transcript("Mismatch - only $gBugs in same state can be merged:\n". $mismatch."\n"); + $errors++; &cancelbug; @newmergelist=(); last; } push(@newmergelist,$ref); @@ -952,6 +1051,7 @@ END if ($data->{package} ne $master_bug_data->{package}) { &transcript("Mismatch - only $gBugs in the same package can be forcibly merged:\n". "$gBug $ref is not in the same package as $master_bug\n"); + $errors++; &cancelbug; @newmergelist=(); last; } for my $t (split /\s+/,$data->{keywords}) { @@ -993,7 +1093,8 @@ END $bug_affected{$ref} = 1; if (&setbug) { if (length($data->{mergedwith})) { - &transcript("$gBug is marked as being merged with others.\n\n"); + &transcript("$gBug is marked as being merged with others. Use an existing clone.\n\n"); + $errors++; &nochangebug; } else { &filelock("nextnumber.lock"); @@ -1045,7 +1146,7 @@ END } } } - } elsif (m/^package\s+(\S.*\S)?\s*$/i) { + } elsif (m/^package\:?\s+(\S.*\S)?\s*$/i) { $ok++; my @pkgs = split /\s+/, $1; if (scalar(@pkgs) > 0) { @@ -1093,8 +1194,56 @@ END &nochangebug; } } + } elsif (m/^unarchive\s+#?(\d+)$/i) { + $ok++; + $ref = $1; + $bug_affected{$ref} = 1; + my $transcript; + eval { + bug_unarchive(bug => $ref, + transcript => \$transcript, + affected_bugs => \%bug_affected, + requester => $header{from}, + request_addr => $controlrequestaddr, + message => \@log, + ); + }; + if ($@) { + $errors++; + } + transcript($transcript."\n"); + } elsif (m/^archive\s+#?(\d+)$/i) { + $ok++; + $ref = $1; + $bug_affected{$ref} = 1; + if (&setbug) { + if (exists $data->{unarchived}) { + my $transcript; + nochangebug(); + eval { + bug_archive(bug => $ref, + transcript => \$transcript, + ignore_time => 1, + affected_bugs => \%bug_affected, + requester => $header{from}, + request_addr => $controlrequestaddr, + message => \@log, + ); + }; + if ($@) { + $errors++; + } + transcript($transcript."\n"); + } + else { + transcript("$gBug $ref has not been archived previously\n\n"); + nochangebug(); + $errors++; + } + } } else { &transcript("Unknown command or malformed arguments to command.\n\n"); + $errors++; if (++$unknowns >= 5) { &transcript("Too many unknown commands, stopping here.\n\n"); last; @@ -1105,6 +1254,7 @@ if ($procline>$#bodylines) { &transcript(">\nEnd of message, stopping processing here.\n\n"); } if (!$ok && !quickabort) { + $errors++; &transcript("No commands successfully parsed; sending the help text(s).\n"); &sendhelp; &transcript("\n"); @@ -1138,6 +1288,16 @@ if (@maintccs) { $maintccs .= "Cc: " . join(",\n ",@maintccs) . "\n"; } +my %packagepr; +for my $maint (keys %maintccreasons) { + for my $package (keys %{$maintccreasons{$maint}}) { + next unless length $package; + $packagepr{$package} = 1; + } +} +my $packagepr = ''; +$packagepr = "X-${gProject}-PR-Package: " . join(keys %packagepr) . "\n" if keys %packagepr; + # Add Bcc's to subscribed bugs push @bcc, map {"bugs=$_\@$gListDomain"} keys %bug_affected; @@ -1145,15 +1305,18 @@ if (!defined $header{'subject'} || $header{'subject'} eq "") { $header{'subject'} = "your mail"; } +# Error text here advertises how many errors there were +my $error_text = $errors > 0 ? " (with $errors errors)":''; + $reply= < Precedence: bulk -X-$gProject-PR-Message: transcript +${packagepr}X-$gProject-PR-Message: transcript ${transcript}Please contact me if you need assistance. @@ -1163,16 +1326,17 @@ $extras END $repliedshow= join(', ',$replyto,@maintccaddrs); +# -1 is the service.in log &filelock("lock/-1"); open(AP,">>db-h/-1.log") || &quit("open db-h/-1.log: $!"); print(AP "\2\n$repliedshow\n\5\n$reply\n\3\n". "\6\n". "Request received from ". - &sani($header{'from'})."\n". - "to ".&sani($controlrequestaddr)."\n". + html_escape($header{'from'})."\n". + "to ".html_escape($controlrequestaddr)."\n". "\3\n". - "\7\n",@{escapelog(@log)},"\n\3\n") || &quit("writing db-h/-1.log: $!"); + "\7\n",escape_log(@log),"\n\3\n") || &quit("writing db-h/-1.log: $!"); close(AP) || &quit("open db-h/-1.log: $!"); &unfilelock; utime(time,time,"db-h"); @@ -1190,6 +1354,45 @@ sub sendmailmessage { $midix++; } +sub fill_template{ + my ($template,$extra_var) = @_; + $extra_var ||={}; + my $variables = {config => \%config, + defined($ref)?(ref => $ref):(), + defined($data)?(data => $data):(), + %{$extra_var}, + }; + my $hole_var = {'&bugurl' => + sub{"$_[0]: ". + 'http://'.$config{cgi_domain}.'/'. + Debbugs::CGI::bug_url($_[0]); + } + }; + return fill_in_template(template => $template, + variables => $variables, + hole_var => $hole_var, + ); +} + +=head2 message_body_template + + message_body_template('mail/ack',{ref=>'foo'}); + +Creates a message body using a template + +=cut + +sub message_body_template{ + my ($template,$extra_var) = @_; + $extra_var ||={}; + my $body = fill_template($template,$extra_var); + return fill_template('mail/message_body', + {%{$extra_var}, + body => $body, + }, + ); +} + sub sendhelp { &sendtxthelpraw("bug-log-mailserver.txt","instructions for request\@$gEmailDomain"); &sendtxthelpraw("bug-maint-mailcontrol.txt","instructions for control\@$gEmailDomain") @@ -1222,6 +1425,7 @@ sub checkmatch { sub checkpkglimit { if (keys %limit_pkgs and not defined $limit_pkgs{$data->{package}}) { &transcript("$gBug number $ref belongs to package $data->{package}, skipping.\n\n"); + $errors++; return 0; } return 1; @@ -1350,7 +1554,7 @@ sub getnextbug { # &transcript("$action\n\n") # endmerge -sub notfoundbug { &transcript("$gBug number $ref not found.\n\n"); } +sub notfoundbug { &transcript("$gBug number $ref not found. (Is it archived?)\n\n"); } sub foundbug { &transcript("$gBug#$ref: $data->{subject}\n"); } sub getmerge { @@ -1372,7 +1576,8 @@ sub endmerge { sub getbug { &dlen("getbug $ref"); $lowstate eq 'idle' || die "$state ?"; - if (($data = &lockreadbug($ref))) { + # Only use unmerged bugs here + if (($data = &lockreadbug($ref,'db-h'))) { $sref= $ref; $lowstate= "open"; &dlex("getbug => 1"); @@ -1397,16 +1602,13 @@ sub savebug { $lowstate eq 'open' || die "$lowstate ?"; length($action) || die; $ref == $sref || die "read $sref but saving $ref ?"; - my $hash = get_hashname($ref); - open(L,">>db-h/$hash/$ref.log") || &quit("opening db-h/$hash/$ref.log: $!"); - print(L - "\6\n". - "".&sani($action)."\n". - "Request was from ".&sani($header{'from'})."\n". - "to ".&sani($controlrequestaddr).". \n". - "\3\n". - "\7\n",@{escapelog(@log)},"\n\3\n") || &quit("writing db-h/$hash/$ref.log: $!"); - close(L) || &quit("closing db-h/$hash/$ref.log: $!"); + append_action_to_log(bug => $ref, + action => $action, + requester => $header{from}, + request_addr => $controlrequestaddr, + message => \@log, + get_lock => 0, + ); unlockwritebug($ref, $data); $lowstate= "idle"; &dlex("savebug");