+if ($subcommand ne 'help') {
+ chdir($config{spool_dir}) or die "chdir $config{spool_dir} failed: $!";
+}
+my $opts =
+ handle_subcommand_arguments(\@ARGV,
+ $subcommands{$subcommand}{arguments},
+ $subcommands{$subcommand}{defaults},
+ );
+$subcommands{$subcommand}{function}->(\%options,$opts,\%config,\@ARGV);
+
+
+sub mark_ham {
+ mark_it('ham',@_);
+}
+
+sub mark_spam {
+ mark_it('spam',@_);
+}
+
+sub mark_it {
+ my ($spam_ham,$options,$opts,$config,$argv) = @_;
+ my $regex = shift @{$argv};
+ for my $bug_num (@{$argv}) {
+ my $spam = Debbugs::Log::Spam->new(bug_num => $bug_num) or
+ die "Unable to open bug log spam for $bug_num";
+ foreachmsg(sub {
+ my ($bn,$rec,$mid) = @_;
+ my $body = $rec->{text};
+ my ($subject) = $body =~ /^Subject: *(.+)$/mi;
+ my $is_match = 0;
+ if ($subject =~ /\Q$regex\E/) {
+ $is_match = 1;
+ }
+ if ($mid =~ /\Q$regex\E/) {
+ $is_match = 1;
+ }
+ if ($spam_ham eq 'spam') {
+ $spam->add_spam($mid);
+ } else {
+ $spam->add_ham($mid);
+ }
+ },
+ $bug_num
+ );
+ }
+}
+
+
+sub score_bug {
+ my ($options,$opts,$config,$argv) = @_;
+ for my $bug_num (@{$argv}) {
+ my @bug_score =
+ spam_score_bug($bug_num,
+ $options->{spamc},
+ $options->{spamc_opts},
+ $opts->{skip_seen},
+ );
+ print "$_->{score} $_->{message_id} $_->{subject}\n"
+ foreach @bug_score;
+ }
+}
+
+sub auto_spamscan {
+ my ($options,$opts,$config,$argv) = @_;
+
+ for my $bug_num (@{$argv}) {
+ my $spam = Debbugs::Log::Spam->new(bug_num => $bug_num) or
+ die "Unable to open bug log spam for $bug_num";
+ foreachmsg(sub {
+ my ($bn,$rec,$mid) = @_;
+ if ($spam->is_spam($mid)) {
+ print STDERR "already spam\n" if $DEBUG;
+ return;
+ }
+ if ($spam->is_ham($mid)) {
+ print STDERR "already ham\n" if $DEBUG;
+ return;
+ }
+ my ($score,$is_spam,$report,$threshold) =
+ spam_score($rec,
+ $options->{spamc},
+ $options->{spamc_opts},
+ );
+ if ($is_spam) {
+ print STDERR "it's spam ($score)\n" if $DEBUG;
+ $spam->add_spam($mid);
+ } elsif ($score < $opts->{ham_threshold}) {
+ print STDERR "it's really ham ($score)\n" if $DEBUG;
+ $spam->add_ham($mid);
+ }
+ else {
+ print STDERR "it's ham ($score)\n" if $DEBUG;
+ }
+ },
+ $bug_num,
+ );
+ $spam->save();
+ }
+}
+
+sub spam_score_bug {
+ my ($bug,$spamc,$spamc_opts,$skip_seen) = @_;
+
+ my $spam;
+ if ($skip_seen) {
+ $spam = Debbugs::Log::Spam->new(bug_num => $bug) or
+ die "Unable to open bug log spam for $bug";
+ }
+ my @records;
+ foreachmsg(sub {
+ my ($bn,$rec,$mid) = @_;
+ my $score;
+ if ($skip_seen) {
+ if ($spam->is_spam($mid)) {
+ $score = 999;
+ } elsif ($spam->is_ham($mid)) {
+ $score = -999;
+ }
+ }
+ $score //=
+ spam_score($rec,$spamc,$spamc_opts);
+ my ($subject) = $rec->{text} =~ /^Subject: *(.+)$/mi;
+ push @records,
+ {message_id => $mid,
+ score => $score,
+ subject => $subject,
+ };
+ },
+ $bug
+ );
+ return @records;
+}
+
+sub spam_score {
+ my ($record,$spamc,$spamc_opts) = @_;
+ my ($score,$threshold,$report);
+ my $is_spam = 0;
+ eval {
+ my ($spamc_in,$spamc_out);
+ my $old_sig = $SIG{"PIPE"};
+ $SIG{"PIPE"} = sub {
+ die "SIGPIPE in child for some reason";
+ };
+ my $childpid =
+ open3($spamc_in,$spamc_out,0,
+ $spamc,'-E',@{$spamc_opts}) or
+ die "Unable to fork spamc: $!";
+ if (not $childpid) {
+ die "Unable to fork spamc";
+ }
+ print {$spamc_in} $record->{text};
+ close($spamc_in) or die "Unable to close spamc_in: $!";
+ waitpid($childpid,0);
+ if ($? >> 8) {
+ $is_spam = 1;
+ }
+ my ($first_line,@report) = <$spamc_out>;
+ if ($DEBUG) {
+ print STDERR "[$?;".($? >> 8)."] ";
+ print STDERR $first_line,@report;
+ print STDERR " ";
+ }
+ if (defined $first_line) {
+ chomp $first_line;
+ ($score,$threshold) = $first_line =~ m{^(-?[\d\.]+)/(-?[\d\.]+)$};
+ $report = join('',@report);
+ }
+ close($spamc_out);
+ $SIG{"PIPE"} = $old_sig;
+ };
+ if ($@) {
+ carp "processing of message failed [$@]\n";
+ return undef;
+ }
+ return wantarray?($score,$is_spam,$report):$score;
+}