Skip to content

INN: Cancel-Lock und Cancel-Key

Im Rahmen der notwendigen Umbaumaßnahmen und der Neueinrichtung von news.szaf.org habe ich endlich auch die Unterstützung von Cancel-Lock und Cancel-Key umgesetzt.

Cancel sind Steuernachrichten zur Löschung anderer Nachrichten im Usenet, vorgesehen zur Löschung eigener Beiträge (oder ggf. noch zur Löschung von Beiträgen durch den Newsadministrator des einspeisenden Servers). Das Protokoll sieht aber keine Verifizierung dieser Nachrichten vor; grundsätzlich kann also jeder Teilnehmer Nachrichten von jedem anderen Teilnehmer löschen, was zwar unzulässig ist und auf breite Ablehnung stößt, aber dennoch in der Vergangenheit gerne vieltausendfach genutzt wurde, nicht nur für von der Netzgemeinschaft akzeptierte Spamcancel, sondern auch für Attacken gegen bestimmte Newsgroups ("leercanceln") oder Netzteilnehmer. Die als Reaktion oft erfolgte Abschaltung der Cancel-Funktionalität war aber auch nicht der Weisheit letzter Schluß.

Daher wurden bereits Ende der Neunziger Vorschläge gemacht, wie das Canceln von Beiträgen nur für Befugte ermöglicht werden kann, nämlich durch das Hinzufügen eines kryptographischen Cancel-Locks in jedem Beitrag, zu dem eine Cancelnachricht den passenden Cancel-Key enthalten muß, wenn sie ausgeführt werden soll. Cancel-Lock und -Key sind dabei zusammengesetzt aus einem geheimen Schlüssel und der Message-ID des jeweiligen (bzw. zu cancelnden) Postings. Nachdem diese Vorschläge jedenfalls auf Client-Seite nie breite Umsetzung - außerhalb einiger unixoider Newsreader - fanden (und sich das Prinzio vermutlich deshalb auch auf Serverseite nicht durchsetzte), bekamen Cancel-Lock und -Key Ende Juni 2007 durch deren verpflichtende Einführung bei dem bedeutenden deutschen Newsserver news.individual.de neuen Auftrieb. Dort hat man nämlich nicht nur die Überprüfung von Cancel-Locks aktiviert, sondern zugleich hinsichtlich der fehlenden Unterstützung im Client zumindest für die eigenen Nutzer insoweit Abhilfe geschaffen als Cancel-Locks durch den Server (!) automatisch allen Beiträgen hinzugefügt werden (und bei berechtigten Canceln wird in gleicher Weise automatisch der passende Cancel-Key eingetragen).

Bereits kurz danach wurden in de.comm.software.newsserver - namentlich durch Alexander Bartolich - erste Implementationen sowohl zum Einfügen als auch zum Prüfen von Cancel-Lock und -Key für den INN veröffentlicht:

Grundsätzlich sind für die Überprüfung von Cancel-Lock und -Key zwei Varianten denkbar: Entweder läßt man die Cancelverarbeitung in Betrieb und filtert unerwünschte Cancel (und Supersedes!) im filter_innd aus, oder man deaktiviert die Cancel-Verarbeitung komplett und führt nur erwünschte Cancel aus filter_innd heraus aus. Letzteres ermöglicht die weitere Unterscheidung, ob man Cancel ablehnen oder zwar annehmen und weiterverbreiten, aber nicht ausführen oder annehmen und ausführen möchte. Weiterhin muß man sich entscheiden, wie man mit Postings ohne Cancel-Lock umgehen will. Für diese Postings kann man weiterhin alle Cancel akzeptieren - oder keinen. (Und man muß sich eine Lösung für Spamcancel überlegen; entweder akzeptiert man signierte Cancel aus vertrauenswürdiger Quelle, oder man implementiert zugleich NoCeM als Ergänzung.)

Update: Die aktuellen Fassungen gibt es in meinem Git-Repository. Dort sind auch Anpassungen für Debian Wheezy eingepflegt.

Egal, wie man zu der Überprüfung von Cancel-Lock und -Key steht: zumindest das automatische Einfügen derselben in Postings der eigenen Benutzer (Cancel-LOck für jedes Posting, Cancel-Key für Cancel und Supersedes) ist in keinem Fall schädlich und durchaus hilfreich, eben weil es Server gibt, die Cancel nur noch mit passendem Cancel-Key ausführen. Es bietet sich an, diese Funktionalität für den INN im filter_nnrpd zu implementieren, dem Filter also, den alle, aber auch nur die lokal geposteten Beiträge durchlaufen. Eine denkbare Implementation ist die folgende für filter_nnrpd.pl, basierend auf dem Posting von Alexander Bartolich:

  1.     #
  2.     # Do any initialization steps.
  3.     #
  4.     use Digest::MD5  qw(md5_base64);
  5.     use Digest::SHA1();
  6.     use Digest::HMAC_SHA1();
  7.     use MIME::Base64();
  8.  
  9.     $CANCEL_LOCK = 'hereasecretword';
  10.  
  11.     #
  12.     # Filter
  13.     #
  14.     sub filter_post {
  15.        my $rval = "" ;             # assume we'll accept.
  16.        $modify_headers = 1;
  17.  
  18.        [...]
  19.  
  20.        # Cancel-Lock / Cancel-Key
  21.        add_cancel_lock(\%hdr, $user);
  22.          
  23.        if (exists( $hdr{"Control"} ) && $hdr{"Control"} =~ m/^cancel\s+(<[^>]+>)/i) {
  24.           my $key = calc_cancel_key($user, $1);
  25.           add_cancel_item(\%hdr, 'Cancel-Key', $key);
  26.        }
  27.        elsif (exists( $hdr{"Supersedes"} )) {
  28.           my $key = calc_cancel_key($user, $hdr{"Supersedes"});
  29.           add_cancel_item(\%hdr, 'Cancel-Key', $key);
  30.        }
  31.          
  32.        return $rval;
  33.     }
  34.  
  35.     #
  36.     # Cancel-Lock / Cancel-Key
  37.     #
  38.     sub add_cancel_item($$$) {
  39.        my ( $r_hdr, $name, $value ) = @_;
  40.        my $prefix = $r_hdr->{$name};
  41.        $prefix = defined($prefix) ? $prefix . ' sha1:' : 'sha1:';
  42.        $r_hdr->;{$name} = $prefix . $value;
  43.     }
  44.  
  45.     sub calc_cancel_key($$) {
  46.        my ( $user, $message_id ) = @_;
  47.        return MIME::Base64::encode(Digest::HMAC_SHA1::hmac_sha1($message_id, $user . $CANCEL_LOCK), '');
  48.     }
  49.  
  50.     sub add_cancel_lock($$) {
  51.        my ( $r_hdr, $user ) = @_;
  52.        my $key = calc_cancel_key($user, $r_hdr->{'Message-ID'});
  53.        my $lock = MIME::Base64::encode(Digest::SHA1::sha1($key), '');
  54.        add_cancel_item($r_hdr, 'Cancel-Lock', $lock);
  55.     }

Dort, wo oben [...] steht, können andere Teile des Filters eingefügt werden.

Die Überprüfung kann bspw. in cleanfeed in die cleanfood.local eingebunden werden; zunächst sind wieder die notwendigen Perl-Module einzubinden:

  1.     use MIME::Base64();
  2.     use Digest::SHA1();
  3.     use Digest::HMAC_SHA1();

Danach wird in local_filter_cancel() die Validität des Cancels geprüft und in local_filter_after_emp() die Validität von Supersedes:

  1.     #
  2.     # local_filter_cancel
  3.     #
  4.     sub local_filter_cancel {
  5.        unless($hdr{Control} =~ m/^cancel\s+(<[^>]+>)/i) {
  6.           return "Cancel with broken target ID";
  7.        }
  8.        return verify_cancel(\%hdr, $1, 'Cancel');
  9.     }
  10.  
  11.     sub local_filter_after_emp {
  12.        if (exists( $hdr{'Supersedes'} )) {
  13.           #return verify_cancel(\%hdr, $hdr{'Supersedes'}, 'Supersedes');
  14.           # verify_cancel is called, but not returned, so the
  15.           # posting is unconditionally accepted
  16.           # verify_cancel calls INN:cancel() if verification suceeds
  17.           verify_cancel(\%hdr, $hdr{'Supersedes'}, 'Supersedes');
  18.        }
  19.  
  20.        return undef;
  21.     }

Diese Variante setzt voraus, dass die Cancelverarbeitung in INN deaktiviert ist (innflags: -C in inn.conf setzen und INN neu starten). Sie akzeptiert nur Cancel mit passendem Cancel-Key; Supersedes werden in jedem Fall angenommen, aber nur ausgeführt, wenn sie über einen passenden Cancel-Key verfügen. Bei aktivierter Cancelverarbeitung sind die Zeilen 13 und 17 auszutauschen, d.h. die eine ist durch Entfernen des Kommentarzeichens zu aktivieren, die andere auszukommentieren.

Die eigentliche "Arbeit" erfolgt dann in den folgenden zusätzlich noch einzufügenden Funktionen:

  1.     sub verify_cancel($$$) {
  2.        my $r_hdr = shift || die;
  3.        my $target = shift;
  4.        my $descr = shift;
  5.  
  6.        my $headers = INN::head($target) || return "$descr of non-existing ID $target";
  7.  
  8.        my %headers;
  9.        for my $line(split(/\s*\n/, $headers))    {
  10.           if ($line =~ m/^([[:alnum:]-]+):\s+(.*)/) {
  11.              $headers{$1} = $2;
  12.           }
  13.        }
  14.  
  15.        my $lock = $headers{'Cancel-Lock'};
  16.        if (defined($lock)) {
  17.           my $key = $r_hdr->{'Cancel-Key'} || return "$descr of $target without Cancel-Key";
  18.           #return verify_cancel_key($key, $lock, ' target=' . $target);
  19.           return verify_cancel_key($key, $lock, $target);
  20.        }
  21.  
  22.        return undef;
  23.     }
  24.  
  25.     sub verify_cancel_key($$$) {
  26.        my $cancel_key = shift;
  27.        my $cancel_lock = shift;
  28.        my $msg = shift;
  29.  
  30.        $msg = '' unless(defined($msg));
  31.        # -thh
  32.        my $target = $msg;
  33.        $msg = ' target=' . $msg;
  34.  
  35.        my %lock;
  36.        for my $l(split(/\s+/, $cancel_lock))   {
  37.           next unless($l =~ m/^(sha1|md5):(\S+)/);
  38.           $lock{$2} = $1;
  39.        }
  40.  
  41.        for my $k(split(/\s+/, $cancel_key))    {
  42.           unless($k =~ m/^(sha1|md5):(\S+)/) {
  43.             INN::syslog('notice', "Invalid Cancel-Key syntax '$k'.$msg");
  44.             next;
  45.           }
  46.  
  47.           my $key;
  48.           if ($1 eq 'sha1') {
  49.              $key = Digest::SHA1::sha1($2); }
  50.           elsif ($1 eq 'md5') {
  51.              $key = Digest::MD5::md5($2);
  52.           }
  53.           $key = MIME::Base64::encode_base64($key, '');
  54.  
  55.           if (exists($lock{$key})) {
  56.              INN::syslog('notice', "Valid Cancel-Key $key found.$msg");
  57.              # -thh
  58.              # article is canceled now
  59.              INN::cancel($target) if ($target);
  60.              return undef;
  61.           }
  62.        }
  63.  
  64.        INN::syslog('notice',
  65.           "No Cancel-Key[$cancel_key] matches Cancel-Lock[$cancel_lock]$msg"
  66.        );
  67.        return "No Cancel-Key matches Cancel-Lock.$msg";
  68.     }
  69.  

Auch diese Funktionen sind für eine generell deaktivierte Cancelverarbeitung geschrieben; wenn die Cancelverarbeitung aktiv ist, ist die Zeile 59 wegzulassen oder auszukommentieren.

Auch der Code für cleanfeed stammt im wesentlichen von Alexander Bartolich und ansonsten von "Ray Banana".

(Die obigen Codeteile sind aus Postings zusammengetragen, getestet und für meine eigenen Zwecke angepaßt; dieser Blog-Beitrag soll eine mögliche Umsetzung von Cancel-Lock und -Key vorstellen, die ich im Rahmen der Neueinrichtung meines Newsserver vorgenommen habe. Er kann und will keine Darstellung des Für und Wider und der verschiedenen Möglichkeiten des Umgangs damit bieten.)

Trackbacks

Netz - Rettung - Recht am : INN: NoCeMs verarbeiten

Vorschau anzeigen
Ergänzend zur bereits im November eingerichteten Auswertung von Cancel-Lock und -Key auf news.szaf.org habe ich heute die Auswertung von NoCeMs eingerichtet. NoCeM (“No See ‘Em”) sind, grob umrissen, digital signierte Usenet-Beiträge in

Kommentare

Ansicht der Kommentare: Linear | Verschachtelt

Noch keine Kommentare

Kommentar schreiben

HTML-Tags werden in ihre Entities umgewandelt.
Markdown-Formatierung erlaubt
Standard-Text Smilies wie :-) und ;-) werden zu Bildern konvertiert.
BBCode-Formatierung erlaubt
Gravatar, Identicon/Ycon Autoren-Bilder werden unterstützt.
Formular-Optionen