#! /usr/bin/perl -w
# written April 1996, tale@uunet.uu.net (David C Lawrence)
# Version 1.4
#
# 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

$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.
# $ENV{'PGPPATH'} = '/path/to/your/pgp/config';

$tmp = "/tmp/pgp$$";

### Exit value:
### 0  good signature
### 1  no signature
### 2  unknown signature
### 3  bad signature
### 255 problem not directly related to pgp analysis of signature

die "Usage: $0 < message\n" if @ARGV != 0;

$0 =~ s(^.*/)();                # trim /path/to/prog to prog

# this is, by design, case-sensitive with regards to the headers it checks.
# it's also insistent about the colon-space rule.
while (<>) {
  chop;
  last if /^$/;
  if (/^(\S+):[ \t](.+)/) {
    ($label, $value) = ($1, $2);
    $dup{$label} = 1 if $header{$label};
    $header{$label} = $value;
  } elsif (/^\s/) {
    &fail("$0: non-header line at $_\n") unless $label;
    $header{$label} .= "\n$_";
  } else {
    &fail("$0: non-header line at $_\n");
  }
}

$pgpheader = "X-PGP-Sig";
exit 1 unless $_ = $header{$pgpheader}; # no signature

# the regexp below might be too strict about the structure of pgp sig lines

# 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*
$sep = "[ \t]*(\n?[ \t]+)+";

# match all of the characters in a radix64 string
$r64 = '[a-zA-Z0-9+/]';

&fail("$0: $pgpheader not in expected format\n")
  unless /^(\S+)$sep(\S+)(($sep$r64{64})+$sep$r64+=?=?$sep=$r64{4})$/;

($version, $signed_headers, $signature) = ($1, $3, $4);
$signature =~ s/$sep/\n/g;

$message  = "-----BEGIN PGP SIGNED MESSAGE-----\n\n";
$message .= "X-Signed-Headers: $signed_headers\n";
foreach $label (split(",", $signed_headers)) {
  &fail("$0: duplicate signed $label header, can't verify\n")
    if $dup{$label};
  $message .= "$label: ";
  $message .= "$header{$label}" if $header{$label};
  $message .= "\n";
}
$message .= "\n";               # end of headers

while (<>) {                    # read body lines
  s/^-/- -/;                    # pgp quote ("ASCII armor") dashes
  $message .= $_;               # append to output string
}

$message .= "\n-----BEGIN PGP SIGNATURE-----\n";
$message .= "Version: $version\n";
$message .= $signature;
$message .= "\n-----END PGP SIGNATURE-----\n";

open(TMP,"> $tmp") || &fail("$0: open > $tmp: $!\n");

-f TMP ||
  &fail("$0: $tmp not a plain file, possible security violation attempt\n");
(stat(_))[3] == 1 ||
  &fail("$0: $tmp has hard links, possible security violation attempt\n");

print TMP $message;
close(TMP) || warn "$0: close > $tmp: $!\n";
&fail("$0: write error for message to check\n")
  if -s $tmp != length($message);

$ENV{'LANGUAGE'} = 'en';        # force English for pgp messages
$ok = 2;                        # unknown signature result is default
open(PGP,"$pgp -f < $tmp 2>&1 >/dev/null |") ||
  &fail("$0: failed to execute pgp: $!\n");

$/ = "\n";
while (<PGP>) {
  # MIT PGP 2.6.2:
  #   Good signature from user "Robert Braver <rbraver@ohww.norman.ok.us>".
  # ViaCrypt PGP 4.0:
  #   Good signature from user:  Robert Braver <rbraver@ohww.norman.ok.us>
  if (/^Good signature from user(:  (.*)| "(.*)"\.)$/) {
    $ok = 0;
    $signer = $+;
  } elsif (/^Bad signature /) {
    $ok = 3;
  } elsif (/^.?(Keyring file .*? does not exist)/) {
    warn "$0: pgp error: $1.\n";
    $ok = 255;
  }
}
close(PGP) || warn "$0: closing pgp pipe returned status $?\n";
unlink("$tmp") || warn "$0: unlink $tmp: $!\n";

print "$signer\n" if $signer;
exit $ok;

sub
fail

{
  unlink($tmp);
  print STDERR $_[0];
  exit 255;
}

# Our lawyer told me to include the following.  The upshot of it is
# that you can use the software for free as much as you like.

# 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.
