#! /usr/bin/perl -w # written April 1996, tale@isc.org (David C Lawrence) # mostly rewritten 2001-03-21 by Marco d'Itri # done some changes to handle different digest-algorithms, if found # in X-PGP-Hash headerline. Also rewritten for human use instead of # INN-Use -- 2006-03-02 Nils Ketelsen # # requirements: # - GnuPG # - perl 5.004_03 # # 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, # set appropriately the next line. my $keyring = $ENV{HOME}.'/.gnupg/pubring.gpg'; my $tmpdir = $ENV{TMP}||$ENV{TEMP}||"/tmp/"; ### 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 = $tmpdir . "/pgp$$"; 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; 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}; unless ($_) { print "\nMessage was not signed\n"; exit 1; } # 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"; $message .= "Hash: $header{'X-PGP-Hash'}\n" if $header{'X-PGP-Hash'}; $message .= "\n"; $message .= "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 STDERR $message; print TMP $message; close TMP or die("close $tmp: $!"); # my $opts = '--quiet --status-fd=1 --logger-fd=1'; my $opts .= " --keyring=$keyring" if $keyring; print "\n"; open(PGP, "$gpg $opts $tmp |") or fail("failed to execute $gpg: $!"); undef $/; $_ = ; unlink $tmp or die("unlink $tmp: $!"); if (not close PGP) { if ($? >> 8) { my $status = $? >> 8; die("gpg exited status $status") if $status > 1; } else { die('gpg died on signal ' . ($? & 255)); } } print STDOUT "\n$_"; 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 fail { 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.