#!/usr/bin/perl -w require '/etc/news/innshellvars.pl'; # written April 1996, tale@isc.org (David C Lawrence) # mostly rewritten 2001-03-21 by Marco d'Itri # # requirements: # - GnuPG # - perl 5.004_03 and working Sys::Syslog # - syslog daemon # # There is no locking because gpg is supposed to not need it and controlchan # will serialize control messages processing anyway. require 5.004_03; use strict; # if you keep your keyring somewhere that is not the default used by gpg, # change the location below. my $keyring; if ($inn::newsetc && -d "$inn::newsetc/pgp") { $keyring = $inn::newsetc . '/pgp/pubring.gpg'; } # If you have INN and the script is able to successfully include your # innshellvars.pl file, the value of the next two variables will be # overridden. my $tmpdir = '/var/log/news/'; my $syslog_facility = 'news'; # 1: print PGP output my $debug = 0; #$debug = 1 if -t 1; ### Exit value: ### 0 good signature ### 1 no signature ### 2 unknown signature ### 3 bad signature ### 255 problem not directly related to gpg analysis of signature ############################################################################## ################ NO USER SERVICEABLE PARTS BELOW THIS COMMENT ################ ############################################################################## my $tmp = ($inn::pathtmp ? $inn::pathtmp : $tmpdir) . "/pgp$$"; $syslog_facility = $inn::syslog_facility if $inn::syslog_facility; my $nntp_format = 0; $0 =~ s#^.*/##; # trim /path/to/prog to prog die "Usage: $0 < message\n" if $#ARGV != -1; # Path to gpg binary my $gpg; if ($inn::gpgv) { $gpg = $inn::gpgv; } else { foreach (split(/:/, $ENV{PATH}), qw(/usr/local/bin /opt/gnu/bin)) { if (-x "$_/gpgv") { $gpg = "$_/gpgv"; last; } } } fail('cannot find the gpgv binary') if not $gpg; # this is, by design, case-sensitive with regards to the headers it checks. # it's also insistent about the colon-space rule. my ($label, $value, %dup, %header); while () { # if a header line ends with \r\n, this article is in the encoding # it would be in during an NNTP session. some article storage # managers keep them this way for efficiency. $nntp_format = /\r\n$/ if $. == 1; s/\r?\n$//; last if /^$/; if (/^(\S+):[ \t](.+)/) { ($label, $value) = ($1, $2); $dup{$label} = 1 if $header{$label}; $header{$label} = $value; } elsif (/^\s/) { fail("non-header at line $.: $_") unless $label; $header{$label} .= "\n$_"; } else { fail("non-header at line $.: $_"); } } my $pgpheader = 'X-PGP-Sig'; $_ = $header{$pgpheader}; exit 1 if not $_; # no signature # 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 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 my $r64 = '[a-zA-Z0-9+/]'; fail("$pgpheader not in expected format") unless /^(\S+)$sep(\S+)(($sep$r64{64})+$sep$r64+=?=?$sep=$r64{4})$/; my ($version, $signed_headers, $signature) = ($1, $3, $4); $signature =~ s/$sep/\n/g; my $message = "-----BEGIN PGP SIGNED MESSAGE-----\n\n" . "X-Signed-Headers: $signed_headers\n"; foreach $label (split(',', $signed_headers)) { fail("duplicate signed $label header, can't verify") if $dup{$label}; $message .= "$label: "; $message .= $header{$label} if $header{$label}; $message .= "\n"; } $message .= "\n"; # end of headers while () { # read body lines 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 last if $_ eq ".\r\n"; # remove NNTP encoding s/^\.\./\./; s/\r\n$/\n/; } s/^-/- -/; # pgp quote ("ASCII armor") dashes $message .= $_; } $message .= "\n-----BEGIN PGP SIGNATURE-----\n" . "Version: $version\n" . $signature . "\n-----END PGP SIGNATURE-----\n"; open(TMP, ">$tmp") or fail("open $tmp: $!"); print TMP $message; close TMP or errmsg("close $tmp: $!"); my $opts = '--quiet --status-fd=1 --logger-fd=1'; $opts .= " --keyring=$keyring" if $keyring; open(PGP, "$gpg $opts $tmp |") or fail("failed to execute $gpg: $!"); undef $/; $_ = ; unlink $tmp or errmsg("unlink $tmp: $!"); if (not close PGP) { if ($? >> 8) { my $status = $? >> 8; errmsg("gpg exited status $status") if $status > 1; } else { errmsg('gpg died on signal ' . ($? & 255)); } } print STDERR $_ if $debug; my $ok = 255; # default exit status my $signer; if (/^\[GNUPG:\]\s+GOODSIG\s+\S+\s+(\S+)/m) { $ok = 0; $signer = $1; } elsif (/^\[GNUPG:\]\s+NODATA/m or /^\[GNUPG:\]\s+UNEXPECTED/m) { $ok = 1; } elsif (/^\[GNUPG:\]\s+NO_PUBKEY/m) { $ok = 2; } elsif (/^\[GNUPG:\]\s+BADSIG\s+/m) { $ok = 3; } print "$signer\n" if $signer; exit $ok; sub errmsg { my $msg = $_[0]; eval 'use Sys::Syslog qw(:DEFAULT setlogsock)'; die "$0: cannot use Sys::Syslog: $@ [$msg]\n" if $@; die "$0: cannot set syslog method [$msg]\n" if not (setlogsock('unix') or setlogsock('inet')); $msg .= " processing $header{'Message-ID'}" if $header{'Message-ID'}; openlog($0, 'pid', $syslog_facility); syslog('err', '%s', $msg); closelog(); } sub fail { errmsg($_[0]); unlink $tmp; exit 255; } __END__ # Copyright 2000 by Marco d'Itri # License of the original version distributed by David C. Lawrence: # Copyright (c) 1996 UUNET Technologies, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. All advertising materials mentioning features or use of this software # must display the following acknowledgement: # This product includes software developed by UUNET Technologies, Inc. # 4. The name of UUNET Technologies ("UUNET") may not be used to endorse or # promote products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY UUNET ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL UUNET BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # OF THE POSSIBILITY OF SUCH DAMAGE.