+++ /dev/null
-#!/usr/bin/perl -w
-# fixscript will replace this line with require innshellvars.pl
-
-##############################################################################
-# perl-nocem - a NoCeM-on-spool implementation for INN 2.x.
-# Copyright 2000 by Miquel van Smoorenburg <miquels@cistron.nl>
-# Copyright 2001 by Marco d'Itri <md@linux.it>
-# This program is licensed under the terms of the GNU General Public License.
-#
-# List of changes:
-#
-# 2002: Patch by Steven M. Christey for untrusted printf input.
-# 2007: Patch by Christoph Biedl for checking a timeout.
-# Documentation improved by Jeffrey M. Vinocur (2002), Russ Allbery (2006)
-# and Julien Elie (2007).
-#
-##############################################################################
-
-require 5.00403;
-use strict;
-
-# XXX FIXME I haven't been able to load it only when installed.
-# If nobody can't fix it just ship the program with this line commented.
-#use Time::HiRes qw(time);
-
-my $keyring = $inn::pathetc . '/pgp/ncmring.gpg';
-
-# XXX To be moved to a config file.
-#sub local_want_cancel_id {
-# my ($group, $hdrs) = @_;
-#
-## Hippo has too many false positives to be useful outside of pr0n groups
-# if ($hdrs->{issuer} =~ /(?:Ultra|Spam)Hippo/) {
-# foreach (split(/,/, $group)) {
-# return 1 if /^alt\.(?:binar|sex)/;
-# }
-# return 0;
-# }
-# return 1;
-#}
-
-# no user serviceable parts below this line ###################################
-
-# global variables
-my ($working, $got_sighup, $got_sigterm, @ncmperm, $cancel);
-my $use_syslog = 0;
-my $log_open = 0;
-my $nntp_open = 0;
-my $last_cancel = 0;
-my $socket_timeout = $inn::peertimeout - 100;
-
-my $logfile = $inn::pathlog . '/perl-nocem.log';
-
-# initialization and main loop ###############################################
-
-eval { require Sys::Syslog; import Sys::Syslog; $use_syslog = 1; };
-
-if ($use_syslog) {
- eval "sub Sys::Syslog::_PATH_LOG { '/dev/log' }" if $^O eq 'dec_osf';
- Sys::Syslog::setlogsock('unix') if $^O =~ /linux|dec_osf/;
- openlog('nocem', '', $inn::syslog_facility);
-}
-
-if (not $inn::gpgv) {
- logmsg('cannot find the gpgv binary', 'err');
- sleep 5;
- exit 1;
-}
-
-if ($inn::version and not $inn::version =~ /^INN 2\.[0123]\./) {
- $cancel = \&cancel_nntp;
-} else {
- $cancel = \&cancel_ctlinnd;
-}
-
-$SIG{HUP} = \&hup_handler;
-$SIG{INT} = \&term_handler;
-$SIG{TERM} = \&term_handler;
-$SIG{PIPE} = \&term_handler;
-
-logmsg('starting up');
-
-unless (read_ctlfile()) {
- sleep 5;
- exit 1;
-}
-
-while (<STDIN>) {
- chop;
- $working = 1;
- do_nocem($_);
- $working = 0;
- term_handler() if $got_sigterm;
- hup_handler() if $got_sighup;
-}
-
-logmsg('exiting because of EOF', 'debug');
-exit 0;
-
-##############################################################################
-
-# Process one NoCeM notice.
-sub do_nocem {
- my $token = shift;
- my $start = time;
-
- # open the article and verify the notice
- my $artfh = open_article($token);
- return if not defined $artfh;
- my ($msgid, $nid, $issuer, $nocems) = read_nocem($artfh);
- close $artfh;
- return unless $nocems;
-
- &$cancel($nocems);
- logmsg("Articles cancelled: " . join(' ', @$nocems), 'debug');
- my $diff = (time - $start) || 0.01;
- my $nr = scalar @$nocems;
- logmsg(sprintf("processed notice %s by %s (%d ids, %.5f s, %.1f/s)",
- $nid, $issuer, $nr, $diff, $nr / $diff));
-}
-
-# - Check if it is a PGP signed NoCeM notice
-# - See if we want it
-# - Then check PGP signature
-sub read_nocem {
- my $artfh = shift;
-
- # Examine the first 200 lines to see if it is a PGP signed NoCeM.
- my $ispgp = 0;
- my $isncm = 0;
- my $inhdr = 1;
- my $i = 0;
- my $body = '';
- my ($from, $msgid);
- while (<$artfh>) {
- last if $i++ > 200;
- s/\r\n$/\n/;
- if ($inhdr) {
- if (/^$/) {
- $inhdr = 0;
- } elsif (/^From:\s+(.*)\s*$/i) {
- $from = $1;
- } elsif (/^Message-ID:\s+(<.*>)/i) {
- $msgid = $1;
- }
- } else {
- $body .= $_;
- $ispgp = 1 if /^-----BEGIN PGP SIGNED MESSAGE-----/;
- if (/^\@\@BEGIN NCM HEADERS/) {
- $isncm = 1;
- last;
- }
- }
- }
-
- # must be a PGP signed NoCeM.
- if (not $ispgp) {
- logmsg("Article $msgid: not PGP signed", 'debug');
- return;
- }
- if (not $isncm) {
- logmsg("Article $msgid: not a NoCeM", 'debug');
- return;
- }
-
- # read the headers of this NoCeM, and check if it's supported.
- my %hdrs;
- while (<$artfh>) {
- s/\r\n/\n/;
- $body .= $_;
- last if /^\@\@BEGIN NCM BODY/;
- my ($key, $val) = /^([^:]+)\s*:\s*(.*)$/;
- $hdrs{lc $key} = $val;
- }
- foreach (qw(action issuer notice-id type version)) {
- next if $hdrs{$_};
- logmsg("Article $msgid: missing $_ pseudo header", 'debug');
- return;
- }
- return if not supported_nocem($msgid, \%hdrs);
-
- # decide if we want it.
- if (not want_nocem(\%hdrs)) {
- logmsg("Article $msgid: unwanted ($hdrs{issuer}/$hdrs{type})", 'debug');
- return;
- }
-# XXX want_hier() not implemented
-# if ($hdrs{hierarchies} and not want_hier($hdrs{hierarchies})) {
-# logmsg("Article $msgid: unwanted hierarchy ($hdrs{hierarchies})",
-# 'debug');
-# return;
-# }
-
- # We do want it, so read the entire article. Also copy it to
- # a temp file so that we can check the PGP signature when done.
- my $tmpfile = "$inn::pathtmp/nocem.$$";
- if (not open(OFD, ">$tmpfile")) {
- logmsg("cannot open temp file $tmpfile: $!", 'err');
- return;
- }
- print OFD $body;
- undef $body;
-
- # process NoCeM body.
- my $inbody = 1;
- my @nocems;
- my ($lastid, $lastgrp);
- while (<$artfh>) {
- s/\r\n$/\n/;
- print OFD;
- $inbody = 0 if /^\@\@END NCM BODY/;
- next if not $inbody or /^#/;
-
- my ($id, $grp) = /^(\S*)\s+(\S+)/;
- next if not $grp;
- if ($id) {
- push @nocems, $lastid
- if $lastid and want_cancel_id($lastgrp, \%hdrs);
- $lastid = $id;
- $lastgrp = $grp;
- } else {
- $lastgrp .= ',' . $grp;
- }
- }
- push @nocems, $lastid if $lastid and want_cancel_id($lastgrp, \%hdrs);
- close OFD;
-
- # at this point we need to verify the PGP signature.
- return if not @nocems;
- my $e = pgp_check($hdrs{issuer}, $msgid, $tmpfile);
- unlink $tmpfile;
- return if not $e;
-
- return ($msgid, $hdrs{'notice-id'}, $hdrs{issuer}, \@nocems);
-}
-
-# XXX not implemented: code to discard notices for groups we don't carry
-sub want_cancel_id {
- my ($group, $hdrs) = @_;
-
- return local_want_cancel_id(@_) if defined &local_want_cancel_id;
- 1;
-}
-
-# Do we actually want this NoCeM?
-sub want_nocem {
- my $hdrs = shift;
-
- foreach (@ncmperm) {
- my ($issuer, $type) = split(/\001/);
- if ($hdrs->{issuer} =~ /$issuer/i) {
- return 1 if '*' eq $type or lc $hdrs->{type} eq $type;
- }
- }
- return 0;
-}
-
-sub supported_nocem {
- my ($msgid, $hdrs) = @_;
-
- if ($hdrs->{version} !~ /^0\.9[0-9]?$/) {
- logmsg("Article $msgid: version $hdrs->{version} not supported",
- 'debug');
- return 0;
- }
- if ($hdrs->{action} ne 'hide') {
- logmsg("Article $msgid: action $hdrs->{action} not supported",
- 'debug');
- return 0;
- }
- return 1;
-}
-
-# Check the PGP signature on an article.
-sub pgp_check {
- my ($issuer, $msgid, $art) = @_;
-
- # fork and spawn a child
- my $pid = open(PFD, '-|');
- if (not defined $pid) {
- logmsg("pgp_check: cannot fork: $!", 'err');
- return 0;
- }
- if ($pid == 0) {
- open(STDERR, '>&STDOUT');
- exec($inn::gpgv, '--status-fd=1',
- $keyring ? '--keyring=' . $keyring : '', $art);
- exit 126;
- }
-
- # Read the result and check status code.
- local $_ = join('', <PFD>);
- my $status = 0;
- if (not close PFD) {
- if ($? >> 8) {
- $status = $? >> 8;
- } else {
- logmsg("Article $msgid: $inn::gpgv killed by signal " . ($? & 255));
- return 0;
- }
- }
-# logmsg("Command line was: $inn::gpgv --status-fd=1"
-# . ($keyring ? ' --keyring=' . $keyring : '') . " $art", 'debug');
-# logmsg("Full PGP output: >>>$_<<<", 'debug');
-
- if (/^\[GNUPG:\]\s+GOODSIG\s+\S+\s+(.*)/m) {
- return 1 if $1 =~ /\Q$issuer\E/;
- logmsg("Article $msgid: signed by $1 instead of $issuer");
- } elsif (/^\[GNUPG:\]\s+NO_PUBKEY\s+(\S+)/m) {
- logmsg("Article $msgid: $issuer (ID $1) not in keyring");
- } elsif (/^\[GNUPG:\]\s+BADSIG\s+\S+\s+(.*)/m) {
- logmsg("Article $msgid: bad signature from $1");
- } elsif (/^\[GNUPG:\]\s+BADARMOR/m or /^\[GNUPG:\]\s+UNEXPECTED/m) {
- logmsg("Article $msgid: malformed signature");
- } elsif (/^\[GNUPG:\]\s+ERRSIG\s+(\S+)/m) {
- # safety net: we get there if we don't know about some token
- logmsg("Article $msgid: unknown error (ID $1)");
- } else {
- # some other error we don't know about happened.
- # 126 is returned by the child if exec fails.
- s/ at \S+ line \d+\.\n$//; s/\n/_/;
- logmsg("Article $msgid: $inn::gpgv exited "
- . (($status == 126) ? "($_)" : "with status $status"), 'err');
- }
- return 0;
-}
-
-# Read article.
-sub open_article {
- my $token = shift;
-
- if ($token =~ /^\@.+\@$/) {
- my $pid = open(ART, '-|');
- if ($pid < 0) {
- logmsg('Cannot fork: ' . $!, 'err');
- return undef;
- }
- if ($pid == 0) {
- exec("$inn::newsbin/sm", '-q', $token) or
- logmsg("Cannot exec sm: $!", 'err');
- return undef;
- }
- return *ART;
- } else {
- return *ART if open(ART, $token);
- logmsg("Cannot open article $token: $!", 'err');
- }
- return undef;
-}
-
-# Cancel a number of Message-IDs. We use ctlinnd to do this,
-# and we run up to 15 of them at the same time (10 usually).
-sub cancel_ctlinnd {
- my @ids = @{$_[0]};
-
- while (@ids > 0) {
- my $max = @ids <= 15 ? @ids : 10;
- for (my $i = 1; $i <= $max; $i++) {
- my $msgid = shift @ids;
- my $pid;
- sleep 5 until (defined ($pid = fork));
- if ($pid == 0) {
- exec "$inn::pathbin/ctlinnd", '-s', '-t', '180',
- 'cancel', $msgid;
- exit 126;
- }
-# logmsg("cancelled: $msgid [$i/$max]", 'debug');
- }
- # Now wait for all children.
- while ((my $pid = wait) > 0) {
- next unless $?;
- if ($? >> 8) {
- logmsg("Child $pid died with status " . ($? >> 8), 'err');
- } else {
- logmsg("Child $pid killed by signal " . ($? & 255), 'err');
- }
- }
- }
-}
-
-sub cancel_nntp {
- my $ids = shift;
- my $r;
-
- if ($nntp_open and time - $socket_timeout > $last_cancel) {
- logmsg('Close socket for timeout');
- close (NNTP);
- $nntp_open = 0;
- }
- if (not $nntp_open) {
- use Socket;
- if (not socket(NNTP, PF_UNIX, SOCK_STREAM, 0)) {
- logmsg("socket: $!", 'err');
- goto ERR;
- }
- if (not connect(NNTP, sockaddr_un($inn::pathrun . '/nntpin'))) {
- logmsg("connect: $!", 'err');
- goto ERR;
- }
- if (($r = <NNTP>) !~ /^200 /) {
- $r =~ s/\r\n$//;
- logmsg("bad reply from server: $r", 'err');
- goto ERR;
- }
- select NNTP; $| = 1; select STDOUT;
- print NNTP "MODE CANCEL\r\n";
- if (($r = <NNTP>) !~ /^284 /) {
- $r =~ s/\r\n$//;
- logmsg("MODE CANCEL not supported: $r", 'err');
- goto ERR;
- }
- $nntp_open = 1;
- }
- foreach (@$ids) {
- print NNTP "$_\r\n";
- if (($r = <NNTP>) !~ /^289/) {
- $r =~ s/\r\n$//;
- logmsg("cannot cancel $_: $r", 'err');
- goto ERR;
- }
- }
- $last_cancel = time;
- return;
-
-ERR:
- # discard unusable socket
- close (NNTP);
- logmsg('Switching to ctlinnd...', 'err');
- cancel_ctlinnd($ids);
- $cancel = \&cancel_ctlinnd;
-}
-
-sub read_ctlfile {
- my $permfile = $inn::pathetc . '/nocem.ctl';
-
- unless (open(CTLFILE, $permfile)) {
- logmsg("Cannot open $permfile: $!", 'err');
- return 0;
- }
- while (<CTLFILE>) {
- chop;
- s/^\s+//; s/\s+$//;
- next if /^#/ or /^$/;
- my ($issuer, $type) = split(/:/, lc $_);
- logmsg("Cannot parse nocem.ctl line <<$_>>", 'err')
- if not $issuer and $type;
- $type =~ s/\s//g;
- push @ncmperm, "$issuer\001$_" foreach split(/,/, $type);
- }
- close CTLFILE;
- return 1;
-}
-
-sub logmsg {
- my ($msg, $lvl) = @_;
-
- if (not $use_syslog) {
- if ($log_open == 0) {
- open(LOG, ">>$logfile") or die "Cannot open log: $!";
- $log_open = 1;
- select LOG; $| = 1; select STDOUT;
- }
- $lvl ||= 'notice';
- print LOG "$lvl: $msg\n";
- return;
- }
- syslog($lvl || 'notice', '%s', $msg);
-}
-
-sub hup_handler {
- $got_sighup = 1;
- return if $working;
- close LOG;
- $log_open = 0;
-}
-
-sub term_handler {
- $got_sigterm = 1;
- return if $working;
- logmsg('exiting because of signal');
- exit 1;
-}
-
-# lint food
-print $inn::pathrun.$inn::pathlog.$inn::pathetc.$inn::newsbin.$inn::pathbin
- .$inn::pathtmp.$inn::peertimeout.$inn::syslog_facility;
-
-__END__
-
-=head1 NAME
-
-perl-nocem - A NoCeM-on-spool implementation for S<INN 2.x>
-
-=head1 SYNOPSIS
-
-perl-nocem
-
-=head1 DESCRIPTION
-
-NoCeM, which is pronounced I<No See 'Em>, is a protocol enabling
-authenticated third-parties to issue notices which can be used
-to cancel unwanted articles (like spam and articles in moderated
-newsgroups which were not approved by their moderators). It can
-also be used by readers as a I<third-party killfile>. It is
-intended to eventually replace the protocol for third-party cancel
-messages.
-
-B<perl-nocem> processes third-party, PGP-signed article cancellation
-notices. It is possible not to honour all NoCeM notices but only those
-which are sent by people whom you trust (that is to say if you trust
-the PGP key they use to sign their NoCeM notices). Indeed, it is up
-to you to decide whether you wish to honour their notices, depending
-on the criteria they use.
-
-Processing NoCeM notices is easy to set up:
-
-=over 4
-
-=item 1.
-
-Import the keys of the NoCeM issuers you trust in order to check
-the authenticity of their notices. You can do:
-
- gpg --no-default-keyring --primary-keyring <pathetc>/pgp/ncmring.gpg --import <key-file>
-
-where <pathetc> is the value of the I<pathetc> parameter set in F<inn.conf>
-and <key-file> the file containing the key(s) to import. The keyring
-must be located in I<pathetc>/pgp/ncmring.gpg (create the directory
-before using B<gpg>). For old PGP-generated keys, you may have to use
-B<--allow-non-selfsigned-uid> if they are not properly self-signed,
-but anyone creating a key really should self-sign the key. Current
-PGP implementations do this automatically.
-
-The keys of NoCeM issuers can be found in the web site of I<The NoCeM Registry>:
-L<http://www.xs4all.nl/~rosalind/nocemreg/nocemreg.html>. You can even
-download there a unique file which contains all the keys.
-
-=item 2.
-
-Create a F<nocem.ctl> config file in I<pathetc> indicating the NoCeM issuers
-and notices you want to follow. This permission file contains lines like:
-
- annihilator-1:*
- clewis@ferret.ocunix:mmf
- stephane@asynchrone:mmf,openproxy,spam
-
-This will remove all articles for which the issuer (first part of the line,
-before the colon C<:>) has issued NoCeM notices corresponding to the
-criteria specified after the colon.
-
-You will also find information about that on the web site of
-I<The NoCeM Registry>.
-
-=item 3.
-
-Add to the F<newsfeeds> file an entry like this one in order to feed
-B<perl-nocem> the NoCeM notices posted to alt.nocem.misc and
-news.lists.filters:
-
- nocem!\
- :!*,alt.nocem.misc,news.lists.filters\
- :Tc,Wf,Ap:<pathbin>/perl-nocem
-
-with the correct path to B<perl-nocem>, located in <pathbin>. Then, reload
-the F<newsfeeds> file (C<ctlinnd reload newsfeeds 'NoCeM channel feed'>).
-
-Note that you should at least carry news.lists.filters on your news
-server (or other newsgroups where NoCeM notices are sent) if you wish
-to process them.
-
-=item 4.
-
-Everything should now work. However, do not hesitate to manually test
-B<perl-nocem> with a NoCeM notice, using:
-
- grephistory '<Message-ID>' | perl-nocem
-
-Indeed, B<perl-nocem> expects tokens on its standard input, and
-B<grephistory> can easily give it the token of a known article,
-thanks to its Message-ID.
-
-=back
-
-When you have verified that everything works, you can eventually turn
-off regular spam cancels, if you want, not processing any longer
-cancels containing C<cyberspam> in the Path: header (see the
-I<refusecybercancels> parameter in F<inn.conf>).
-
-=head1 FILES
-
-=over 4
-
-=item I<pathbin>/perl-nocem
-
-The Perl script itself used to process NoCeM notices.
-
-=item I<pathetc>/nocem.ctl
-
-The configuration file which specifies the NoCeM notices to be processed.
-
-=item I<pathetc>/pgp/ncmring.gpg
-
-The keyring which contains the public keys of trusted NoCeM issuers.
-
-=back
-
-=head1 BUGS
-
-The Subject: header is not checked for the @@NCM string and there is no
-check for the presence of the References: header.
-
-The Newsgroups: pseudo header is not checked, but this can be done in
-local_want_cancel_id().
-
-The Hierarchies: header is ignored.
-
-=head1 HISTORY
-
-Copyright 2000 by Miquel van Smoorenburg <miquels@cistron.nl>.
-
-Copyright 2001 by Marco d'Itri <md@linux.it>.
-
-$Id: perl-nocem.in 7733 2008-04-06 09:16:20Z iulius $
-
-=head1 SEE ALSO
-
-gpgv(1), grephistory(1), inn.conf(5), newsfeeds(5), pgp(1).
-
-=cut