Import version pgpverify 1.29 from INN

Changes include:

- Disambiguate numbered lists from description lists in POD to silent
  a pod2man warning.

- Add a --findid=<string> flag to explicitly search for <string> in the
  output from PGP's analysis of the message.  In case the signature is
  valid but does not contain <string>, pgpverify exits with the new
  exit status 4.

- Use the INN::Config Perl module instead of innshellvars.pl to
  accomodate the new build process of INN 2.5.

plus coding style, comment style, and whitespace cleanups.
This commit is contained in:
Russ Allbery 2016-10-17 16:13:30 -07:00
parent 8b3fb9f827
commit 46f63786db

256
pgpverify
View file

@ -1,10 +1,10 @@
#! /usr/bin/perl -w
# do '@LIBDIR@/innshellvars.pl';
# If running inside INN, uncomment the above and point to innshellvars.pl.
# use lib '@LIBPERLDIR@'; use INN::Config;
# If running inside INN, uncomment the above and point to INN::Config.
#
# Written April 1996, tale@isc.org (David C Lawrence)
# Written April 1996, <tale@isc.org> (David C Lawrence)
# Currently maintained by Russ Allbery <eagle@eyrie.org>
# Version 1.27, 2005-07-02
# Version 1.29, 2014-04-15
#
# NOTICE TO INN MAINTAINERS: The version that is shipped with INN is the
# same as the version that I make available to the rest of the world
@ -16,6 +16,18 @@
# me about it; I want to know what old versions of Perl are still used in
# practice.
#
# Changes from 1.28 -> 1.29
# -- Disambiguate numbered lists from description lists in POD to silent
# a pod2man warning.
# -- Add a --findid=<string> flag to explicitly search for <string> in the
# output from PGP's analysis of the message. In case the signature is
# valid but does not contain <string>, pgpverify exits with the new
# exit status 4.
#
# Changes from 1.27 -> 1.28
# -- Use the INN::Config Perl module instead of innshellvars.pl to
# accomodate the new build process of INN 2.5.
#
# Changes from 1.26 -> 1.27
# -- Default to pubring.gpg when trustedkeys.gpg is not found in the
# default key location, for backward compatibility.
@ -35,7 +47,7 @@
# -- Bump version number to match CVS revision number.
# -- Replaced all signature verification code with code that uses detached
# signatures. Signatures generated by GnuPG couldn't be verified using
# attached signatures without adding a Hash header, and this was the
# attached signatures without adding a Hash: header, and this was the
# path of least resistance plus avoids munging problems in the future.
# Code taken from PGP::Sign.
#
@ -61,85 +73,85 @@
# -- Use INN's syslog_facility if available.
#
# Changes from 1.11 -> 1.12
# -- support for GnuPG
# -- Support for GnuPG.
# -- Use /usr/ucb/logger, if present, instead of /usr/bin/logger (the latter
# of which, on Solaris at least, is some sort of brain damaged POSIX.2
# command which doesn't use syslog).
# -- made syslog work for dec_osf (version 4, at least)
# -- fixed up priority of '.' operator vs bitwise operators
# -- Made syslog work for dec_osf (version 4, at least).
# -- Fixed up priority of '.' operator vs bitwise operators.
#
# Changes from 1.10 -> 1.11
# -- code to log error messages to syslog.
# -- Code to log error messages to syslog.
# See $syslog and $syslog_method configurable variables.
# -- configurably allow date stamp on stderr error messages.
# -- added locking for multiple concurrent pgp instances.
# -- more clear error message if pgp exits abnormally.
# -- identify PGP 5 "BAD signature" string.
# -- minor diddling for INN (path to innshellvars.pl changed)
# -- Configurably allow date stamp on stderr error messages.
# -- Added locking for multiple concurrent pgp instances.
# -- More clear error message if pgp exits abnormally.
# -- Identify PGP 5 "BAD signature" string.
# -- Minor diddling for INN (path to innshellvars.pl changed).
#
# Changes from 1.9 -> 1.10
# -- minor diddling for INN 2.0: use $inn'pathtmp if it exists, and
# work with the new subst method to find innshellvars.pl
# -- do not truncate the tmp file when opening, in case it is really
# linked to another file
# -- Minor diddling for INN 2.0: use $inn'pathtmp if it exists, and
# work with the new subst method to find innshellvars.pl.
# -- Do not truncate the tmp file when opening, in case it is really
# linked to another file.
#
# Changes from 1.8 -> 1.9
# -- match 'Bad signature' pgp output to return exit status 3 by removing
# -- Match 'Bad signature' pgp output to return exit status 3 by removing
# '^' in regexp matched on multiline string.
#
# Changes from 1.7 -> 1.8
# -- ignore final dot-CRLF if article is in NNTP format
# -- Ignore final dot-CRLF if article is in NNTP format.
#
# Changes from 1.6 -> 1.7
# -- parse PGP 5.0 'good signature' lines.
# -- allow -test swtich; prints pgp input and output
# -- look for pgp in INN's innshellvars.pl
# -- changed regexp delimiters for stripping $0 to be compatible with old
# perl
# -- Parse PGP 5.0 'good signature' lines.
# -- Allow -test switch; prints pgp input and output.
# -- Look for pgp in INN's innshellvars.pl.
# -- Changed regexp delimiters for stripping $0 to be compatible with old
# Perl.
#
# Changes from 1.5 -> 1.6
# -- handle articles encoded in NNTP format ('.' starting line is doubled,
# -- Handle articles encoded in NNTP format ('.' starting line is doubled,
# \r\n at line end) by stripping NNTP encoding.
# -- exit 255 with pointer to $HOME or $PGPPATH if pgp can't find key
# ring. (probably doesn't match the necessary error message with
# ViaCrypt PGP)
# -- failures also report message-id so the article can be looked up to
# -- Exit 255 with pointer to $HOME or $PGPPATH if pgp can't find key
# ring. (It probably doesn't match the necessary error message with
# ViaCrypt PGP.)
# -- Failures also report Message-ID so the article can be looked up to
# retry.
#
# Changes from 1.4 -> 1.5
# -- force English lanugage for 'Good signature from user' by passing
# -- Force English language for 'Good signature from user' by passing
# +language=en on pgp command line, rather than setting the
# environment variable LANGUAGE to 'en'.
#
# Changes from 1.3 -> 1.4
# -- now handles wrapped headers that have been unfolded.
# (though I do believe news software oughtn't be unfolding them.)
# -- checks to ensure that the temporary file is really a file, and
# not a link or some other weirdness
# -- Now handles wrapped headers that have been unfolded.
# (Though I do believe news software oughtn't be unfolding them.)
# -- Checks to ensure that the temporary file is really a file, and
# not a link or some other weirdness.
# Path to the GnuPG gpgv binary, if you have GnuPG. If you do, this will
# be used in preference to PGP. For most current control messages, you
# need a version of GnuPG that can handle RSA signatures. If you have INN
# and the script is able to successfully include your innshellvars.pl
# file, the value of $inn::gpgv will override this.
# and the script is able to successfully include your INN::Config module,
# the value of $INN::Config::gpgv will override this.
# $gpgv = '/usr/local/bin/gpgv';
# Path to pgp binary; for PGP 5.0, set the path to the pgpv binary. If
# you have INN and the script is able to successfully include your
# innshellvars.pl file, the value of $inn::pgp will override this.
# INN::Config module, the value of $INN::Config::pgp will override this.
$pgp = '/usr/local/bin/pgp';
# If you keep your keyring somewhere that is not the default used by pgp,
# uncomment the next line and set appropriately. If you have INN and the
# script is able to successfully include your innshellvars.pl file, this
# will be set to $inn::newsetc/pgp if that directory exists unless you set
# it explicitly. GnuPG will use a file named pubring.gpg in this
# script is able to successfully include your INN::Config module, this
# will be set to $INN::Config::newsetc/pgp if that directory exists unless
# you set it explicitly. GnuPG will use a file named pubring.gpg in this
# directory.
# $keyring = '/path/to/your/pgp/config';
# If you have INN and the script is able to successfully include your
# innshellvars.pl file, the value of $inn::pathtmp and $inn::locks will
# override these.
# INN::Config module, the value of $INN::Config::pathtmp and
# $INN::Config::locks will override these.
$tmpdir = "/tmp";
$lockdir = $tmpdir;
@ -152,7 +164,7 @@ $lockdir = $tmpdir;
# all that's available up to version 5.004_03. If your syslog does not
# accept UDP log packets, such as when syslogd runs with the -l flag,
# 'inet' will not work. A value of 'unix' will try to contact syslogd
# directly over a Unix domain socket built entirely in perl code (no
# directly over a Unix domain socket built entirely in Perl code (no
# subprocesses). If that is not working for you, and you have the
# 'logger' program on your system, set this variable to its full path name
# to have a subprocess contact syslogd. If the method is just "logger",
@ -163,17 +175,17 @@ $lockdir = $tmpdir;
# /some/text/file" on a file that is not a valid news article. The
# "non-header at line #" error should be syslogged.
#
# $syslog_method = 'unix'; # Unix doman socket, perl5.004_03 or higher
# $syslog_method = 'inet'; # UDP to port 514 of localhost
# $syslog_method = 'unix'; # Unix doman socket, Perl 5.004_03 or higher.
# $syslog_method = 'inet'; # UDP to port 514 of localhost.
# $syslog_method = ''; # Don't ever try to do syslogging.
$syslog_method = 'logger'; # search for the logger program
$syslog_method = 'logger'; # Search for the logger program.
# The next two variables are the values to be used for syslog's facility
# and level to use, as would be found in syslog.conf. For various
# reasons, it is impossible to economically have the script figure out how
# to do syslogging correctly on the machine. If you have INN and the
# script is able to successfully include you innshellvars.pl file, then
# the value of $inn::syslog_facility will override this value of
# script is able to successfully include you INN::Config module, then
# the value of $INN::Config::syslog_facility will override this value of
# $syslog_facility; $syslog_level is unaffected.
$syslog_facility = 'news';
$syslog_level = 'err';
@ -181,9 +193,9 @@ $syslog_level = 'err';
# Prepend the error message with a timestamp? This is only relevant if
# not syslogging, when errors go to stderr.
#
# $log_date = 0; # zero means don't do it.
# $log_date = 1; # non-zero means do it.
$log_date = -t STDOUT; # do it if STDOUT is to a terminal
# $log_date = 0; # Zero means don't do it.
# $log_date = 1; # Non-zero means do it.
$log_date = -t STDOUT; # Do it if STDOUT is to a terminal.
# End of configuration section.
@ -192,32 +204,32 @@ require 5;
use strict;
use vars qw($gpgv $pgp $keyring $tmp $tmpdir $lockdir $syslog_method
$syslog_facility $syslog_level $log_date $test $messageid);
$syslog_facility $syslog_level $log_date $findid $test $messageid);
use Fcntl qw(O_WRONLY O_CREAT O_EXCL);
use FileHandle;
use IPC::Open3 qw(open3);
use POSIX qw(strftime);
use Getopt::Long;
# Turn on test mode if the first argument is '-test'.
if (@ARGV && $ARGV[0] eq '-test') {
shift @ARGV;
$test = 1;
}
# Check the arguments passed to pgpverify.
# If a syntax error occurs, do not syslog it: such an error is almost
# certainly from someone running the script manually.
Getopt::Long::Configure('bundling_override');
GetOptions(
'test' => sub { $test = 1 },
'findid=s' => \$findid
) or die "Usage: $0 [--findid='string'] [--test] < message\n";
# Not syslogged, such an error is almost certainly from someone running
# the script manually.
die "Usage: $0 < message\n" if @ARGV != 0;
# Grab various defaults from innshellvars.pl if running inside INN.
$pgp = $inn::pgp
if $inn::pgp && $inn::pgp ne "no-pgp-found-during-configure";
$gpgv = $inn::gpgv if $inn::gpgv;
$tmp = ($inn::pathtmp ? $inn::pathtmp : $tmpdir) . "/pgp$$";
$lockdir = $inn::locks if $inn::locks;
$syslog_facility = $inn::syslog_facility if $inn::syslog_facility;
if (! $keyring && $inn::newsetc) {
$keyring = $inn::newsetc . '/pgp' if -d $inn::newsetc . '/pgp';
# Grab various defaults from INN::Config if running inside INN.
$pgp = $INN::Config::pgp
if $INN::Config::pgp && $INN::Config::pgp ne "no-pgp-found-during-configure";
$gpgv = $INN::Config::gpgv if $INN::Config::gpgv;
$tmp = ($INN::Config::pathtmp ? $INN::Config::pathtmp : $tmpdir) . "/pgp$$";
$lockdir = $INN::Config::locks if $INN::Config::locks;
$syslog_facility = $INN::Config::syslog_facility if $INN::Config::syslog_facility;
if (! $keyring && $INN::Config::newsetc) {
$keyring = $INN::Config::newsetc . '/pgp' if -d $INN::Config::newsetc . '/pgp';
}
# Trim /path/to/prog to prog for error messages.
@ -310,12 +322,12 @@ sub generate_message {
# The $sep value means the separator between the radix64 signature lines
# can have any amount of spaces or tabs, but must have at least one
# space or tab, if there is a newline then the space or tab has to
# space or tab; if there is a newline then the space or tab has to
# follow the newline. Any number of newlines can appear as long as each
# is followed by at least one space or tab. *phew*
my $sep = "[ \t]*(\n?[ \t]+)+";
# Match all of the characters in a radix64 string
# Match all of the characters in a radix64 string.
my $r64 = '[a-zA-Z0-9+/]';
local $_ = $$header{'X-PGP-Sig'};
@ -341,28 +353,30 @@ sub generate_message {
if ($nntp_format) {
# Check for end of article; some news servers (eg, Highwind's
# "Breeze") include the dot-CRLF of the NNTP protocol in the article
# data passed to this script
# data passed to this script.
last if $_ eq ".\r\n";
# Remove NNTP encoding
# Remove NNTP encoding.
s/^\.\./\./;
s/\r\n$/\n/;
}
$message .= $_;
}
# Strip off all trailing whitespace for compatibility with the way that
# Strip off all trailing whitespaces for compatibility with the way that
# pgpverify used to work, using attached signatures.
$message =~ s/[ \t]+\n/\n/g;
return ($message, $signature, $version);
}
# Check a detatched signature for given data. Takes a signature block (in
# Check a detached signature for given data. Takes a signature block (in
# the form of an ASCII-armored string with embedded newlines), a version
# number (which may be undef), and the message. We return an exit status
# and the key id if the signature verified. 0 means good signature, 1
# means bad data, 2 means an unknown signer, and 3 means a bad signature.
# and the key id if the signature is verified. 0 means good signature, 1
# means bad data, 2 means an unknown signer, 3 means a bad signature, and
# 4 means good signature without having found the argument given to the
# --findid flag.
# In the event of an error, we report with errmsg.
#
# This code is taken almost verbatim from PGP::Sign except for the code to
@ -471,13 +485,13 @@ sub pgp_verify {
# implemented a separate status stream with parseable data.
#
# MIT PGP 2.6.2 and PGP 6.5.2:
# Good signature from user "Russ Allbery <eagle@eyrie.org>".
# Good signature from user "Russ Allbery <rra@stanford.edu>".
# ViaCrypt PGP 4.0:
# Good signature from user: Russ Allbery <eagle@eyrie.org>
# Good signature from user: Russ Allbery <rra@stanford.edu>
# PGP 5.0:
# Good signature made 1999-02-10 03:29 GMT by key:
# 1024 bits, Key ID 0AFC7476, Created 1999-02-10
# "Russ Allbery <eagle@eyrie.org>"
# "Russ Allbery <rra@stanford.edu>"
#
# Also, PGP v2 prints out "Bad signature" while PGP v5 uses "BAD
# signature", and PGP v6 reverts back to "Bad signature".
@ -493,34 +507,45 @@ sub pgp_verify {
$signer = $1;
} elsif (/\[GNUPG:\]\s+NODATA/ || /\[GNUPG:\]\s+UNEXPECTED/) {
$ok = 1;
last;
} elsif (/\[GNUPG:\]\s+NO_PUBKEY/) {
$ok = 2;
last;
} elsif (/\[GNUPG:\]\s+BADSIG\s+/) {
$ok = 3;
last;
}
} else {
if (/^Good signature from user(?::\s+(.*)|\s+\"(.*)\"\.)$/m) {
$signer = $+;
$ok = 0;
last;
} elsif (/^Good signature made .* by key:\n.+\n\s+\"(.*)\"/m) {
$signer = $1;
$ok = 0;
last;
} elsif (/^\S+: Good signature from \"(.*)\"/m) {
$signer = $1;
$ok = 0;
last;
} elsif (/^(?:\S+: )?Bad signature /im) {
$ok = 3;
last;
}
}
# If the --findid flag is used, and the signature is good,
# override the value of the signer with the string specified in
# the --findid flag.
if (defined ($findid) and $ok eq 0) {
$signer = $findid if (/$findid/);
}
}
close $input;
waitpid ($pid, 0);
unlink ($filename, "$filename.asc");
umask $umask;
if (defined ($findid) and $ok eq 0 and $signer ne $findid) {
$ok = 4;
}
return ($ok, $signer || '');
}
@ -558,7 +583,7 @@ sub errmsg {
}
if ($@ || $syslog_method eq '') {
warn $date, "$0: trying to use perl's syslog: $@\n" if $@;
warn $date, "$0: trying to use Perl's syslog: $@\n" if $@;
warn $date, $message, "\n";
warn $date, "... while processing $messageid\n"
if $messageid;
@ -580,7 +605,7 @@ sub errmsg {
}
} else {
# setlogsock arrived in perl 5.004_03 to enable Sys::Syslog to use a
# setlogsock arrived in Perl 5.004_03 to enable Sys::Syslog to use a
# Unix domain socket to talk to syslogd, which is the only way to do
# it when syslog runs with the -l switch.
if ($syslog_method eq "unix") {
@ -629,7 +654,7 @@ sub shlock {
$ltmp = ($file =~ m%(.*/)%)[0] . "shlock$$";
# this should really attempt to use another temp name
# This should really attempt to use another temp name.
-e $ltmp && (unlink($ltmp) || return -1);
open(LTMP, ">$ltmp") || return -1;
@ -645,30 +670,30 @@ sub shlock {
return 0;
}
# ok, the pid in the lockfile is not a number or no longer exists.
# OK, the pid in the lockfile is not a number or no longer exists.
close(LOCK); # silent failure is ok here
# unlink failed
# Unlink failed.
if (unlink($file) != 1 && $! != &ENOENT) {
unlink($ltmp);
return 0;
}
# check if open failed for reason other than file no longer present
# Check if open failed for reason other than file no longer present.
} elsif ($! != &ENOENT) {
unlink($ltmp);
return -1;
}
# either this process unlinked the lockfile because it was bogus, or
# Either this process unlinked the lockfile because it was bogus, or
# between this process's link() and open() the other process holding
# the lock unlinked it. This process can now try to aquire.
# the lock unlinked it. This process can now try to acquire.
if (! link($ltmp, $file)) {
unlink($ltmp);
return $! == &EEXIST ? 0 : -1; # maybe another proc grabbed the lock
return $! == &EEXIST ? 0 : -1; # Maybe another proc grabbed the lock.
}
} else { # first attempt to link failed
} else { # First attempt to link failed.
unlink($ltmp);
return 0;
}
@ -683,7 +708,7 @@ pgpverify - Cryptographically verify Usenet control messages
=head1 SYNOPSIS
B<pgpverify> [B<-test>] < I<message>
B<pgpverify> [B<--findid>=I<string>] [B<--test>] < I<message>
=head1 DESCRIPTION
@ -719,16 +744,31 @@ this behavior.
=head1 OPTIONS
The B<-test> flag causes B<pgpverify> to print out the input that it is
=over 4
=item B<--findid>=I<string>
The B<--findid> flag causes B<pgpverify> to explicitly search for
I<string> in the output from PGP's analysis of the message. This option
is useful when several UIDs are defined on a single PGP key, and the
caller to B<pgpverify> needs checking whether a given one is defined on
this key. In case the signature is valid but does not contain I<string>,
B<pgpverify> exits with exit status 4.
=item B<--test>
The B<--test> flag causes B<pgpverify> to print out the input that it is
passing to PGP (which is a reconstructed version of the input that
supposedly created the control message) as well as the output from PGP's
analysis of the message.
=back
=head1 EXIT STATUS
B<pgpverify> may exit with the following statuses:
=over 5
=over 4
=item 0Z<>
@ -746,6 +786,12 @@ The control message had an unknown PGP signature.
The control message had a bad PGP signature.
=item 4Z<>
The control message had a good PGP signature but the argument given
to the B<--findid> flag had non been found in the output from PGP's
analysis of the message.
=item 255Z<>
A problem occurred not directly related to PGP analysis of signature.
@ -768,21 +814,21 @@ Historically, Usenet news server administrators have configured their news
servers to automatically honor Usenet control messages based on the
originator of the control messages and the hierarchies for which the
control messages applied. For example, in the past, David Lawrence always
issued control messages for the "Big 8" hierarchies (comp, humanities,
issued control messages for the S<"Big 8"> hierarchies (comp, humanities,
misc, news, rec, sci, soc, talk). Usenet news administrators would
configure their news server software to automatically honor newgroup and
rmgroup control messages that originated from David Lawrence and applied
to any of the Big 8 hierarchies.
to any of the S<Big 8> hierarchies.
Unfortunately, Usenet news articles (including control messages) are
notoriously easy to forge. Soon, malicious users realized they could
create or remove (at least temporarily) any Big 8 newsgroup they wanted by
create or remove (at least temporarily) any S<Big 8> newsgroup they wanted by
simply forging an appropriate control message in David Lawrence's name.
As Usenet became more widely used, forgeries became more common.
The B<pgpverify> program was designed to allow Usenet news administrators
to configure their servers to cryptographically verify control messages
before automatically acting on them. Under the pgpverify system, a Usenet
before automatically acting on them. Under the B<pgpverify> system, a Usenet
hierarchy maintainer creates a PGP public/private key pair and
disseminates the public key. Whenever the hierarchy maintainer issues a
control message, he uses the B<signcontrol> program to sign the control
@ -790,7 +836,7 @@ message with the PGP private key. Usenet news administrators configure
their news servers to run the B<pgpverify> program on the appropriate
control messages, and take action based on the PGP key User ID that signed
the control message, not the name and address that appear in the control
message's From or Sender headers.
message's From: or Sender: headers.
Thus, appropriate use of the B<signcontrol> and B<pgpverify> programs
essentially eliminates the possibility of malicious users forging Usenet
@ -821,7 +867,7 @@ Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
=over 3
=over 4
=item 1.
@ -862,7 +908,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=head1 SEE ALSO
gpgv(1), pgp(1)
gpgv(1), pgp(1).
L<ftp://ftp.isc.org/pub/pgpcontrol/> is where the most recent versions of
B<signcontrol> and B<pgpverify> live, along with PGP public keys used for