+
+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,
+ $opts->{spamc},
+ $opts->{spamc_opts});
+ 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 < $options->{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) = @_;
+
+ my @records;
+ foreachmsg(sub {
+ my ($bn,$rec,$mid) = @_;
+ my $score =
+ spam_score($rec,$spamc,$spamc_opts);
+ push @records,
+ {message_id => $mid,
+ score => $score,
+ subject => ($rec->{text} =~ /^Subject: *(.+)/i)[0],
+ };
+ },
+ $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} $record->{text};
+ close($spamc) or die "Unable to close spamc: $!";
+ waitpid($childpid,0);
+ if ($DEBUG) {
+ print STDERR "[$?;".($? >> 8)."] ";
+ print STDERR map {s/\n//; $_ } <$spamc_out>;
+ print STDERR " ";
+ }
+ close($spamc_out);
+ $SIG{"PIPE"} = $old_sig;
+ if ($? >> 8) {
+ $is_spam = 1;
+ }
+ my ($first_line,@report) = <$spamc_out>;
+ if (defined $first_line) {
+ chomp $first_line;
+ ($score,$threshold) = $first_line =~ m{^(-?[\d\.]+)/(-?[\d\.]+)$};
+ $report = join('',@report);
+ }
+ };
+ if ($@) {
+ carp "processing of message failed [$@]\n";
+ return undef;
+ }
+ return wantarray?($score,$is_spam,$report):$score;
+}
+
+sub foreachmsg {
+ my ($sub,$bug_num) = @_;