#! /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;