From 5d910a9d76852cdceff1725077aa97bcddfb6008 Mon Sep 17 00:00:00 2001 From: Thomas Hochstein Date: Sat, 24 Jan 2026 14:37:38 +0100 Subject: [PATCH] Replace canlockcheck with canlockey. - Rewrite. - Add functionalityx to create a Cancel-Key. - Update README. Signed-off-by: Thomas Hochstein --- README.md | 6 +- canlockcheck.pl | 83 --------------------------- canlockey.pl | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 85 deletions(-) delete mode 100644 canlockcheck.pl create mode 100644 canlockey.pl diff --git a/README.md b/README.md index bb90917..ffce066 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ This is a collection of tools around (managing) Netnews. -## canlockcheck +## canlockey -`canlockcheck.pl` will check whether a given Cancel-Lock and Cancel-Key match. +`canlockey.pl` will create a Cancel-Key for a given Message-ID with +optional user, canlock-secret and hash (defaulting to `SHA1`) or +check whether a given Cancel-Lock and Cancel-Key match. ## mew diff --git a/canlockcheck.pl b/canlockcheck.pl deleted file mode 100644 index 5719eea..0000000 --- a/canlockcheck.pl +++ /dev/null @@ -1,83 +0,0 @@ -#! /usr/bin/perl -w -# -# canlockcheck.pl -# -# Check whether Cancel-Lock and Cancel-Key match. -# -# Copyright (c) 2023 Thomas Hochstein - -use MIME::Base64(); -use Digest::SHA(); -use Digest::MD5(); -use Getopt::Long qw(GetOptions); -Getopt::Long::config ('bundling'); - -my $VERSION = "0.1"; - -# read commandline options ############ -my ($OptCanLock,$OptCanKey); -GetOptions ('l|lock=s' => \$OptCanLock, - 'k|key=s' => \$OptCanKey, - 'V|version' => \&ShowVersion) or exit 1; - -# subroutines ######################### - -### display version information and exit -sub ShowVersion { - print "canlockcheck v$VERSION\n"; - print "Copyright (c) 2023 Thomas Hochstein \n"; - print "This program is free software; you may redistribute it ". - "and/or modify it under the same terms as Perl itself.\n"; - exit; -}; - -sub verify_cancel_key($$) { - my $cancel_lock = shift; - my $cancel_key = shift; - - my %lock; - for my $l(split(/\s+/, $cancel_lock)) { - unless($l =~ m/^(sha512|sha256|sha1|md5):(\S+)/) { - printf ("Invalid Cancel-Lock syntax '%s'\n", $l); - next; - } - $lock{$2} = $1; - } - - for my $k(split(/\s+/, $cancel_key)) { - unless($k =~ m/^(sha512|sha256|sha1|md5):(\S+)/) { - printf ("Invalid Cancel-Key syntax '%s'\n", $k); - next; - } - - my $key; - if ($1 eq 'sha512') { - $key = Digest::SHA::sha512($2); - } elsif ($1 eq 'sha256') { - $key = Digest::SHA::sha256($2); - } elsif($1 eq 'sha1') { - $key = Digest::SHA::sha1($2); - } elsif ($1 eq 'md5') { - $key = Digest::MD5::md5($2); - } - $key = MIME::Base64::encode_base64($key, ''); - - if (exists($lock{$key})) { - return sprintf("Cancel-Key %s:%s matches Cancel-Lock %s:%s.", $1, $2, $lock{$key}, $key); - } - } - - return 'No Cancel-Key matches any Cancel-Lock.'; -} - -# Main program ######################## - -if (!$OptCanLock or !$OptCanKey) { - print "canlockcheck -l -k \n"; - exit 2; -} - -my $result = &verify_cancel_key($OptCanLock, $OptCanKey); - -printf ("%s\n", $result); -exit; diff --git a/canlockey.pl b/canlockey.pl new file mode 100644 index 0000000..ca5dc36 --- /dev/null +++ b/canlockey.pl @@ -0,0 +1,148 @@ +#! /usr/bin/perl -w +# +# canlockey.pl +# +# Generate a Cancel-Key for a given Message-ID with a given INN-User +# and secret, or check if a given Cancel-Key matches the Cancel-Lock. +# +# Copyright (c) 2023, 2026 Thomas Hochstein + +use MIME::Base64(); +use Digest::SHA(); +use Digest::MD5 qw(md5); +use Digest::HMAC qw(hmac); +use Getopt::Long qw(GetOptions); +Getopt::Long::config ('bundling'); + +my $VERSION = "0.2"; + +# read commandline options ############ +my ($OptCanLock,$OptCanKey); +GetOptions ('h|hash=s' => \$OptCanHash, + 'k|key=s' => \$OptCanKey, + 'l|lock=s' => \$OptCanLock, + 'm|mid=s' => \$OptMID, + 's|secret=s' => \$OptSecret, + 'u|user=s' => \$OptUser, + 'V|version' => \&ShowVersion) or exit 1; + +# subroutines ######################### + +### display version information and exit +sub ShowVersion { + print "canlockey.pl v$VERSION\n"; + print "Copyright (c) 2023, 2026 Thomas Hochstein \n"; + print "This program is free software; you may redistribute it ". + "and/or modify it under the same terms as Perl itself.\n"; + exit; +}; + +sub create_cancel_key($$$) { + my ( $message_id, $user, $secret, $hash ) = @_; + + # create Cancel-Key + my $key; + if ($hash eq 'sha512') { + $key = Digest::SHA::hmac_sha512($message_id, $user . $secret); + } elsif ($hash eq 'sha256') { + $key = Digest::SHA::hmac_sha256($message_id, $user . $secret); + } elsif($hash eq 'sha1') { + $key = Digest::SHA::hmac_sha1($message_id, $user . $secret); + } elsif ($hash eq 'md5') { + $key = Digest::HMAC::hmac($message_id, $user . $secret); + } + $key = sprintf('%s:%s', $hash, MIME::Base64::encode_base64($key, '')); + return $key; +} + +sub verify_cancel_key($$) { + my ( $cancel_lock, $cancel_key ) = @_; + + # split Cancel-Locks in a hash + # key is Cancel-Lock, value is hash + my %lock; + for my $l(split(/\s+/, $cancel_lock)) { + unless($l =~ m/^(sha512|sha256|sha1|md5):(\S+)/) { + printf ("Invalid Cancel-Lock syntax '%s'\n", $l); + next; + } + $lock{$2} = $1; + } + + # split Cancel-Keys and iterate about the result + # $1 is hash, $2 is Cancel-Key + for my $k(split(/\s+/, $cancel_key)) { + unless($k =~ m/^(sha512|sha256|sha1|md5):(\S+)/) { + printf ("Invalid Cancel-Key syntax '%s'\n", $k); + next; + } + + # calculate Cancel-Lock from Cancel-Key + my $lock; + if ($1 eq 'sha512') { + $lock = Digest::SHA::sha512($2); + } elsif ($1 eq 'sha256') { + $lock = Digest::SHA::sha256($2); + } elsif($1 eq 'sha1') { + $lock = Digest::SHA::sha1($2); + } elsif ($1 eq 'md5') { + $lock = Digest::MD5::md5($2); + } + $lock = MIME::Base64::encode_base64($lock, ''); + + if (exists($lock{$lock})) { + return sprintf("Cancel-Key %s:%s matches Cancel-Lock %s:%s.", $1, $2, $lock{$lock}, $lock); + } + } + + return 'No Cancel-Key matches any Cancel-Lock.'; +} + +# Main program ######################## + +if ($OptCanLock && $OptCanKey) { + ### compare -k to -l + + print "Checking for Cancel-Key that matches Cancel-Lock ...\n"; + printf("%s\n", &verify_cancel_key($OptCanLock, $OptCanKey)); + +} elsif ($OptMID) { + ### create a Cancel-Key + + # die if no (valid) Message-ID has been submitted + die sprintf("Invalid Message-ID: %s\n", $OptMID) + if $OptMID !~ /^<[^\@<>]+\@[^\@<>]+>$/; + + # set defaults for options not given + $OptUser = '' if !$OptUser; + $OptSecret = '' if !$OptSecret; + $OptCanHash = 'sha1' if !$OptCanHash; + + # die if no valid hash has been submitted + die sprintf("Invalid hash type: %s\n", $OptCanHash) + unless $OptCanHash =~ m/^(sha512|sha256|sha1|md5)$/; + + printf("Creating a %s Cancel-Key for user '%s'%sagainst %s ...\n", + $OptCanHash, $OptUser, $OptSecret ? ' ' : ' without secret ', $OptMID); + + my $cancel_key = &create_cancel_key($OptMID, $OptUser, $OptSecret, $OptCanHash); + + printf("Cancel-Key: %s\n", $cancel_key); + + if($OptCanLock) { + printf("%s\n", &verify_cancel_key($OptCanLock, $cancel_key)); + } + +} else { + ### show usage + + print "canlockey.pl v$VERSION\n"; + print "Usage:\n"; + print "1) Check for a matching Cancel-Key:\n"; + print " canlockey -l -k \n"; + print "2) Create a Cancel-Key:\n"; + print " canlockey -m [-u ] [-s ] [-h ] [-l ]\n"; + exit 2; +} + +exit;